94aab86d57
Upon opening, qemu pipe (a.k.a. goldfish pipe) requires a purpose string so that emulator can route the content to the right channel on the host. This CL will ask emulator to send the content to pipe based 'logcat' service on the host. Change-Id: Icc71f81d5b95b64ea315fe10da82ff704416e449
1859 lines
68 KiB
C++
1859 lines
68 KiB
C++
/*
|
|
* 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 <arpa/inet.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/cdefs.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include <atomic>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
#include <cutils/sched_policy.h>
|
|
#include <cutils/sockets.h>
|
|
#include <log/event_tag_map.h>
|
|
#include <log/getopt.h>
|
|
#include <log/logcat.h>
|
|
#include <log/logprint.h>
|
|
#include <private/android_logger.h>
|
|
#include <system/thread_defs.h>
|
|
|
|
#include <pcrecpp.h>
|
|
|
|
#define DEFAULT_MAX_ROTATED_LOGS 4
|
|
|
|
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 = nullptr;
|
|
printed = false;
|
|
logger = nullptr;
|
|
logger_list = nullptr;
|
|
}
|
|
};
|
|
|
|
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<std::string> args;
|
|
std::vector<const char*> argv_hold;
|
|
std::vector<std::string> envs;
|
|
std::vector<const char*> 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;
|
|
log_device_t* devices;
|
|
EventTagMap* eventTagMap;
|
|
// 0 means "infinite"
|
|
size_t maxCount;
|
|
size_t printCount;
|
|
|
|
bool printItAnyways;
|
|
bool debug;
|
|
bool hasOpenedEventTagMap;
|
|
};
|
|
|
|
// 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 nullptr;
|
|
|
|
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)
|
|
|
|
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 = nullptr;
|
|
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 = nullptr;
|
|
}
|
|
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 = nullptr;
|
|
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 = nullptr;
|
|
}
|
|
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) 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)) {
|
|
file0 = android::base::StringPrintf("%s", context->outputFileName);
|
|
} else {
|
|
file0 =
|
|
android::base::StringPrintf("%s.%.*d", context->outputFileName,
|
|
maxRotationCountDigits, i - 1);
|
|
}
|
|
|
|
if (!file0.length() || !file1.length()) {
|
|
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) {
|
|
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(nullptr);
|
|
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) 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>, --file=<file> Log to file. Default is stdout\n"
|
|
" -r <kbytes>, --rotate-kbytes=<kbytes>\n"
|
|
" Rotate log every kbytes. Requires -f option\n"
|
|
" -n <count>, --rotate-count=<count>\n"
|
|
" Sets max number of rotated logs to <count>, default 4\n"
|
|
" --id=<id> If the signature id for logging to file changes, then clear\n"
|
|
" the fileset and continue\n"
|
|
" -v <format>, --format=<format>\n"
|
|
" Sets log print format verb and adverbs, where <format> 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"
|
|
" Multiple -v parameters or comma separated list of format and\n"
|
|
" format modifiers are allowed.\n"
|
|
// private and undocumented nsec, no signal, too much noise
|
|
// useful for -T or -t <timestamp> 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 <expr>, --regex=<expr>\n"
|
|
" Only print lines where the log message matches <expr>\n"
|
|
" where <expr> is a regular expression\n"
|
|
// Leave --head undocumented as alias for -m
|
|
" -m <count>, --max-count=<count>\n"
|
|
" Quit after printing <count> 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 <count> Print only the most recent <count> lines (implies -d)\n"
|
|
" -t '<time>' Print most recent lines since specified time (implies -d)\n"
|
|
" -T <count> Print only the most recent <count> lines (does not imply -d)\n"
|
|
" -T '<time>' Print most recent lines since specified time (not imply -d)\n"
|
|
" count is pure numerical, time is 'MM-DD hh:mm:ss.mmm...'\n"
|
|
" 'YYYY-MM-DD hh:mm:ss.mmm...' or 'sssss.mmm...' format\n"
|
|
" -g, --buffer-size Get the size of the ring buffer.\n"
|
|
" -G <size>, --buffer-size=<size>\n"
|
|
" Set size of log ring buffer, may suffix with K or M.\n"
|
|
" -L, --last Dump logs from prior to last reboot\n"
|
|
// Leave security (Device Owner only installations) and
|
|
// kernel (userdebug and eng) buffers undocumented.
|
|
" -b <buffer>, --buffer=<buffer> Request alternate ring buffer, 'main',\n"
|
|
" 'system', 'radio', 'events', 'crash', 'default' or 'all'.\n"
|
|
" Multiple -b parameters or comma separated list of buffers are\n"
|
|
" allowed. Buffers interleaved. Default -b main,system,crash.\n"
|
|
" -B, --binary Output the log in binary.\n"
|
|
" -S, --statistics Output statistics.\n"
|
|
" -p, --prune Print prune white and ~black list. Service is specified as\n"
|
|
" UID, UID/PID or /PID. Weighed for quicker pruning if prefix\n"
|
|
" with ~, otherwise weighed for longevity if unadorned. All\n"
|
|
" other pruning activity is oldest first. Special case ~!\n"
|
|
" represents an automatic quicker pruning for the noisiest\n"
|
|
" UID as determined by the current statistics.\n"
|
|
" -P '<list> ...', --prune='<list> ...'\n"
|
|
" Set prune white and ~black list, using same format as\n"
|
|
" listed above. Must be quoted.\n"
|
|
" --pid=<pid> Only prints logs from the given pid.\n"
|
|
// Check ANDROID_LOG_WRAP_DEFAULT_TIMEOUT value for match to 2 hours
|
|
" --wrap Sleep for 2 hours or when buffer about to wrap whichever\n"
|
|
" comes first. Improves efficiency of polling by providing\n"
|
|
" an about-to-wrap wakeup.\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"
|
|
" D Debug (default for '*')\n"
|
|
" I Info\n"
|
|
" W Warn\n"
|
|
" E Error\n"
|
|
" F Fatal\n"
|
|
" S Silent (suppress all output)\n"
|
|
"\n'*' by itself means '*:D' and <tag> by itself means <tag>:V.\n"
|
|
"If no '*' filterspec or -s on command line, all filter defaults to '*:V'.\n"
|
|
"eg: '*:S <tag>' prints only <tag>, '<tag>:S' suppresses all <tag> log messages.\n"
|
|
"\nIf not specified on the command line, filterspec is set from ANDROID_LOG_TAGS.\n"
|
|
"\nIf not specified with -v on command line, format is set from ANDROID_PRINTF_LOG\n"
|
|
"or defaults to \"threadtime\"\n\n");
|
|
}
|
|
|
|
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"
|
|
" and individually flagged modifying adverbs can be added:\n"
|
|
" color descriptive epoch monotonic printable uid usec UTC year zone\n"
|
|
"\nSingle format verbs:\n"
|
|
" brief — Display priority/tag and PID of the process issuing the message.\n"
|
|
" long — Display all metadata fields, separate messages with blank lines.\n"
|
|
" process — Display PID only.\n"
|
|
" raw — Display the raw log message, with no other metadata fields.\n"
|
|
" tag — Display the priority/tag only.\n"
|
|
" threadtime — Display the date, invocation time, priority, tag, and the PID\n"
|
|
" and TID of the thread issuing the message. (the default format).\n"
|
|
" time — Display the date, invocation time, priority/tag, and PID of the\n"
|
|
" process issuing the message.\n"
|
|
"\nAdverb modifiers can be used in combination:\n"
|
|
" color — Display in highlighted color to match priority. i.e. \x1B[38;5;231mVERBOSE\n"
|
|
" \x1B[38;5;75mDEBUG \x1B[38;5;40mINFO \x1B[38;5;166mWARNING \x1B[38;5;196mERROR FATAL\x1B[0m\n"
|
|
" descriptive — events logs only, descriptions from event-log-tags database.\n"
|
|
" epoch — Display time as seconds since Jan 1 1970.\n"
|
|
" monotonic — Display time as cpu seconds since last boot.\n"
|
|
" printable — Ensure that any binary logging content is escaped.\n"
|
|
" uid — If permitted, display the UID or Android ID of logged process.\n"
|
|
" usec — Display time down the microsecond precision.\n"
|
|
" UTC — Display time as UTC.\n"
|
|
" year — Add the year to the displayed time.\n"
|
|
" zone — Add the local timezone to the displayed time.\n"
|
|
" \"<zone>\" — Print using this public named timezone (experimental).\n\n"
|
|
);
|
|
}
|
|
// clang-format on
|
|
|
|
static int setLogFormat(android_logcat_context_internal* context,
|
|
const char* formatString) {
|
|
AndroidLogPrintFormat format;
|
|
|
|
format = android_log_formatFromString(formatString);
|
|
|
|
// invalid string?
|
|
if (format == FORMAT_OFF) return -1;
|
|
|
|
return android_log_setPrintFormat(context->logformat, format);
|
|
}
|
|
|
|
static const char multipliers[][2] = { { "" }, { "K" }, { "M" }, { "G" } };
|
|
|
|
static unsigned long value_of_size(unsigned long value) {
|
|
for (unsigned i = 0;
|
|
(i < sizeof(multipliers) / sizeof(multipliers[0])) && (value >= 1024);
|
|
value /= 1024, ++i)
|
|
;
|
|
return value;
|
|
}
|
|
|
|
static const char* multiplier_of_size(unsigned long value) {
|
|
unsigned i;
|
|
for (i = 0;
|
|
(i < sizeof(multipliers) / sizeof(multipliers[0])) && (value >= 1024);
|
|
value /= 1024, ++i)
|
|
;
|
|
return multipliers[i];
|
|
}
|
|
|
|
// String to unsigned int, returns -1 if it fails
|
|
static bool getSizeTArg(const char* ptr, size_t* val, size_t min = 0,
|
|
size_t max = SIZE_MAX) {
|
|
if (!ptr) return false;
|
|
|
|
char* endp;
|
|
errno = 0;
|
|
size_t ret = (size_t)strtoll(ptr, &endp, 0);
|
|
|
|
if (endp[0] || errno) return false;
|
|
|
|
if ((ret > max) || (ret < min)) return false;
|
|
|
|
*val = ret;
|
|
return true;
|
|
}
|
|
|
|
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(context->error, fmt, args);
|
|
va_end(args);
|
|
|
|
switch (showHelp) {
|
|
case HELP_TRUE:
|
|
show_help(context);
|
|
break;
|
|
case HELP_FORMAT:
|
|
show_format_help(context);
|
|
break;
|
|
case HELP_FALSE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
context->stop = true;
|
|
}
|
|
|
|
static char* parseTime(log_time& t, const char* cp) {
|
|
char* ep = t.strptime(cp, "%m-%d %H:%M:%S.%q");
|
|
if (ep) return ep;
|
|
ep = t.strptime(cp, "%Y-%m-%d %H:%M:%S.%q");
|
|
if (ep) return ep;
|
|
return t.strptime(cp, "%s.%q");
|
|
}
|
|
|
|
// Find last logged line in <outputFileName>, or <outputFileName>.1
|
|
static log_time lastLogTime(const char* outputFileName) {
|
|
log_time retval(log_time::EPOCH);
|
|
if (!outputFileName) return retval;
|
|
|
|
std::string directory;
|
|
const char* file = strrchr(outputFileName, '/');
|
|
if (!file) {
|
|
directory = ".";
|
|
file = outputFileName;
|
|
} else {
|
|
directory = std::string(outputFileName, file - outputFileName);
|
|
++file;
|
|
}
|
|
|
|
std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(directory.c_str()),
|
|
closedir);
|
|
if (!dir.get()) return retval;
|
|
|
|
log_time now(android_log_clockid());
|
|
|
|
size_t len = strlen(file);
|
|
log_time modulo(0, NS_PER_SEC);
|
|
struct dirent* dp;
|
|
|
|
while (!!(dp = readdir(dir.get()))) {
|
|
if ((dp->d_type != DT_REG) || !!strncmp(dp->d_name, file, len) ||
|
|
(dp->d_name[len] && ((dp->d_name[len] != '.') ||
|
|
(strtoll(dp->d_name + 1, nullptr, 10) != 1)))) {
|
|
continue;
|
|
}
|
|
|
|
std::string file_name = directory;
|
|
file_name += "/";
|
|
file_name += dp->d_name;
|
|
std::string file;
|
|
if (!android::base::ReadFileToString(file_name, &file)) continue;
|
|
|
|
bool found = false;
|
|
for (const auto& line : android::base::Split(file, "\n")) {
|
|
log_time t(log_time::EPOCH);
|
|
char* ep = parseTime(t, line.c_str());
|
|
if (!ep || (*ep != ' ')) continue;
|
|
// determine the time precision of the logs (eg: msec or usec)
|
|
for (unsigned long mod = 1UL; mod < modulo.tv_nsec; mod *= 10) {
|
|
if (t.tv_nsec % (mod * 10)) {
|
|
modulo.tv_nsec = mod;
|
|
break;
|
|
}
|
|
}
|
|
// We filter any times later than current as we may not have the
|
|
// year stored with each log entry. Also, since it is possible for
|
|
// entries to be recorded out of order (very rare) we select the
|
|
// maximum we find just in case.
|
|
if ((t < now) && (t > retval)) {
|
|
retval = t;
|
|
found = true;
|
|
}
|
|
}
|
|
// We count on the basename file to be the definitive end, so stop here.
|
|
if (!dp->d_name[len] && found) break;
|
|
}
|
|
if (retval == log_time::EPOCH) return retval;
|
|
// tail_time prints matching or higher, round up by the modulo to prevent
|
|
// a replay of the last entry we have just checked.
|
|
retval += modulo;
|
|
return retval;
|
|
}
|
|
|
|
const char* getenv(android_logcat_context_internal* context, const char* name) {
|
|
if (!context->envp || !name || !*name) return nullptr;
|
|
|
|
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 nullptr;
|
|
}
|
|
|
|
} // namespace android
|
|
|
|
void reportErrorName(const char** current, const char* name,
|
|
bool blockSecurity) {
|
|
if (*current) return;
|
|
if (!blockSecurity || (android_name_to_log_id(name) != LOG_ID_SECURITY)) {
|
|
*current = name;
|
|
}
|
|
}
|
|
|
|
static int __logcat(android_logcat_context_internal* context) {
|
|
using namespace android;
|
|
int err;
|
|
bool hasSetLogFormat = false;
|
|
bool clearLog = false;
|
|
bool allSelected = false;
|
|
bool getLogSize = false;
|
|
bool getPruneList = false;
|
|
bool printStatistics = false;
|
|
bool printDividers = false;
|
|
unsigned long setLogSize = 0;
|
|
const char* setPruneList = nullptr;
|
|
const char* setId = nullptr;
|
|
int mode = ANDROID_LOG_RDONLY;
|
|
std::string forceFilters;
|
|
log_device_t* dev;
|
|
struct logger_list* logger_list;
|
|
size_t tail_lines = 0;
|
|
log_time tail_time(log_time::EPOCH);
|
|
size_t pid = 0;
|
|
bool got_t = false;
|
|
|
|
// object instantiations before goto's can happen
|
|
log_device_t unexpected("unexpected", false);
|
|
const char* openDeviceFail = nullptr;
|
|
const char* clearFail = nullptr;
|
|
const char* setSizeFail = nullptr;
|
|
const char* getSizeFail = nullptr;
|
|
int argc = context->argc;
|
|
char* const* argv = context->argv;
|
|
|
|
context->output = stdout;
|
|
context->error = stderr;
|
|
|
|
for (int i = 0; i < argc; ++i) {
|
|
// Simulate shell stderr redirect parsing
|
|
if ((argv[i][0] != '2') || (argv[i][1] != '>')) continue;
|
|
|
|
// Append to file not implemented, just open file
|
|
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]);
|
|
}
|
|
// Only the first one
|
|
break;
|
|
}
|
|
|
|
const char* filename = nullptr;
|
|
for (int i = 0; i < argc; ++i) {
|
|
// Simulate shell stdout redirect parsing
|
|
if (argv[i][0] != '>') continue;
|
|
|
|
// Append to file not implemented, just open file
|
|
filename = &argv[i][(argv[i][1] == '>') + 1];
|
|
// Only the first one
|
|
break;
|
|
}
|
|
|
|
// Deal with setting up file descriptors and FILE pointers
|
|
if (context->error_fd >= 0) { // Is an error file descriptor supplied?
|
|
if (context->error_fd == context->output_fd) {
|
|
context->stderr_stdout = true;
|
|
} else if (context->stderr_null) { // redirection told us to close it
|
|
close(context->error_fd);
|
|
context->error_fd = -1;
|
|
} else { // All Ok, convert error to a FILE pointer
|
|
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) { // Is an output file descriptor supplied?
|
|
if (filename) { // redirect to file, close supplied file descriptor.
|
|
close(context->output_fd);
|
|
context->output_fd = -1;
|
|
} else { // All Ok, convert output to a FILE pointer
|
|
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 (filename) { // We supplied an output file redirected in command line
|
|
context->output = fopen(filename, "web");
|
|
}
|
|
// Deal with 2>&1
|
|
if (context->stderr_stdout) context->error = context->output;
|
|
// Deal with 2>/dev/null
|
|
if (context->stderr_null) {
|
|
context->error_fd = -1;
|
|
context->error = nullptr;
|
|
}
|
|
// Only happens if output=stdout or output=filename
|
|
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 && !strcmp(argv[1], "--help")) {
|
|
show_help(context);
|
|
context->retval = EXIT_SUCCESS;
|
|
goto exit;
|
|
}
|
|
|
|
// meant to catch comma-delimited values, but cast a wider
|
|
// net for stability dealing with possible mistaken inputs.
|
|
static const char delimiters[] = ",:; \t\n\r\f";
|
|
|
|
struct getopt_context optctx;
|
|
INIT_GETOPT_CONTEXT(optctx);
|
|
optctx.opterr = !!context->error;
|
|
optctx.optstderr = context->error;
|
|
|
|
for (;;) {
|
|
int ret;
|
|
|
|
int option_index = 0;
|
|
// list of long-argument only strings for later comparison
|
|
static const char pid_str[] = "pid";
|
|
static const char debug_str[] = "debug";
|
|
static const char id_str[] = "id";
|
|
static const char wrap_str[] = "wrap";
|
|
static const char print_str[] = "print";
|
|
// clang-format off
|
|
static const struct option long_options[] = {
|
|
{ "binary", no_argument, nullptr, 'B' },
|
|
{ "buffer", required_argument, nullptr, 'b' },
|
|
{ "buffer-size", optional_argument, nullptr, 'g' },
|
|
{ "clear", no_argument, nullptr, 'c' },
|
|
{ debug_str, no_argument, nullptr, 0 },
|
|
{ "dividers", no_argument, nullptr, 'D' },
|
|
{ "file", required_argument, nullptr, 'f' },
|
|
{ "format", required_argument, nullptr, 'v' },
|
|
// hidden and undocumented reserved alias for --regex
|
|
{ "grep", required_argument, nullptr, 'e' },
|
|
// hidden and undocumented reserved alias for --max-count
|
|
{ "head", required_argument, nullptr, 'm' },
|
|
{ id_str, required_argument, nullptr, 0 },
|
|
{ "last", no_argument, nullptr, 'L' },
|
|
{ "max-count", required_argument, nullptr, 'm' },
|
|
{ pid_str, required_argument, nullptr, 0 },
|
|
{ print_str, no_argument, nullptr, 0 },
|
|
{ "prune", optional_argument, nullptr, 'p' },
|
|
{ "regex", required_argument, nullptr, 'e' },
|
|
{ "rotate-count", required_argument, nullptr, 'n' },
|
|
{ "rotate-kbytes", required_argument, nullptr, 'r' },
|
|
{ "statistics", no_argument, nullptr, 'S' },
|
|
// hidden and undocumented reserved alias for -t
|
|
{ "tail", required_argument, nullptr, 't' },
|
|
// support, but ignore and do not document, the optional argument
|
|
{ wrap_str, optional_argument, nullptr, 0 },
|
|
{ nullptr, 0, nullptr, 0 }
|
|
};
|
|
// clang-format on
|
|
|
|
ret = getopt_long_r(argc, argv,
|
|
":cdDLt:T:gG:sQf:r:n:v:b:BSpP:m:e:", long_options,
|
|
&option_index, &optctx);
|
|
if (ret < 0) break;
|
|
|
|
switch (ret) {
|
|
case 0:
|
|
// only long options
|
|
if (long_options[option_index].name == pid_str) {
|
|
// ToDo: determine runtime PID_MAX?
|
|
if (!getSizeTArg(optctx.optarg, &pid, 1)) {
|
|
logcat_panic(context, HELP_TRUE, "%s %s out of range\n",
|
|
long_options[option_index].name,
|
|
optctx.optarg);
|
|
goto exit;
|
|
}
|
|
break;
|
|
}
|
|
if (long_options[option_index].name == wrap_str) {
|
|
mode |= ANDROID_LOG_WRAP | ANDROID_LOG_RDONLY |
|
|
ANDROID_LOG_NONBLOCK;
|
|
// ToDo: implement API that supports setting a wrap timeout
|
|
size_t dummy = ANDROID_LOG_WRAP_DEFAULT_TIMEOUT;
|
|
if (optctx.optarg &&
|
|
!getSizeTArg(optctx.optarg, &dummy, 1)) {
|
|
logcat_panic(context, HELP_TRUE, "%s %s out of range\n",
|
|
long_options[option_index].name,
|
|
optctx.optarg);
|
|
goto exit;
|
|
}
|
|
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);
|
|
}
|
|
break;
|
|
}
|
|
if (long_options[option_index].name == print_str) {
|
|
context->printItAnyways = true;
|
|
break;
|
|
}
|
|
if (long_options[option_index].name == debug_str) {
|
|
context->debug = true;
|
|
break;
|
|
}
|
|
if (long_options[option_index].name == id_str) {
|
|
setId = (optctx.optarg && optctx.optarg[0]) ? optctx.optarg
|
|
: nullptr;
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
// default to all silent
|
|
android_log_addFilterRule(context->logformat, "*:s");
|
|
break;
|
|
|
|
case 'c':
|
|
clearLog = true;
|
|
mode |= ANDROID_LOG_WRONLY;
|
|
break;
|
|
|
|
case 'L':
|
|
mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_PSTORE |
|
|
ANDROID_LOG_NONBLOCK;
|
|
break;
|
|
|
|
case 'd':
|
|
mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK;
|
|
break;
|
|
|
|
case 't':
|
|
got_t = true;
|
|
mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK;
|
|
// FALLTHRU
|
|
case 'T':
|
|
if (strspn(optctx.optarg, "0123456789") !=
|
|
strlen(optctx.optarg)) {
|
|
char* cp = parseTime(tail_time, optctx.optarg);
|
|
if (!cp) {
|
|
logcat_panic(context, HELP_FALSE,
|
|
"-%c \"%s\" not in time format\n", ret,
|
|
optctx.optarg);
|
|
goto exit;
|
|
}
|
|
if (*cp) {
|
|
char c = *cp;
|
|
*cp = '\0';
|
|
if (context->error) {
|
|
fprintf(
|
|
context->error,
|
|
"WARNING: -%c \"%s\"\"%c%s\" time truncated\n",
|
|
ret, optctx.optarg, c, cp + 1);
|
|
}
|
|
*cp = c;
|
|
}
|
|
} else {
|
|
if (!getSizeTArg(optctx.optarg, &tail_lines, 1)) {
|
|
if (context->error) {
|
|
fprintf(context->error,
|
|
"WARNING: -%c %s invalid, setting to 1\n",
|
|
ret, optctx.optarg);
|
|
}
|
|
tail_lines = 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'D':
|
|
printDividers = true;
|
|
break;
|
|
|
|
case 'e':
|
|
context->regex = new pcrecpp::RE(optctx.optarg);
|
|
break;
|
|
|
|
case 'm': {
|
|
char* end = nullptr;
|
|
if (!getSizeTArg(optctx.optarg, &context->maxCount)) {
|
|
logcat_panic(context, HELP_FALSE,
|
|
"-%c \"%s\" isn't an "
|
|
"integer greater than zero\n",
|
|
ret, optctx.optarg);
|
|
goto exit;
|
|
}
|
|
} break;
|
|
|
|
case 'g':
|
|
if (!optctx.optarg) {
|
|
getLogSize = true;
|
|
break;
|
|
}
|
|
// FALLTHRU
|
|
|
|
case 'G': {
|
|
char* cp;
|
|
if (strtoll(optctx.optarg, &cp, 0) > 0) {
|
|
setLogSize = strtoll(optctx.optarg, &cp, 0);
|
|
} else {
|
|
setLogSize = 0;
|
|
}
|
|
|
|
switch (*cp) {
|
|
case 'g':
|
|
case 'G':
|
|
setLogSize *= 1024;
|
|
// FALLTHRU
|
|
case 'm':
|
|
case 'M':
|
|
setLogSize *= 1024;
|
|
// FALLTHRU
|
|
case 'k':
|
|
case 'K':
|
|
setLogSize *= 1024;
|
|
// FALLTHRU
|
|
case '\0':
|
|
break;
|
|
|
|
default:
|
|
setLogSize = 0;
|
|
}
|
|
|
|
if (!setLogSize) {
|
|
logcat_panic(context, HELP_FALSE,
|
|
"ERROR: -G <num><multiplier>\n");
|
|
goto exit;
|
|
}
|
|
} break;
|
|
|
|
case 'p':
|
|
if (!optctx.optarg) {
|
|
getPruneList = true;
|
|
break;
|
|
}
|
|
// FALLTHRU
|
|
|
|
case 'P':
|
|
setPruneList = optctx.optarg;
|
|
break;
|
|
|
|
case 'b': {
|
|
std::unique_ptr<char, void (*)(void*)> buffers(
|
|
strdup(optctx.optarg), free);
|
|
char* arg = buffers.get();
|
|
unsigned idMask = 0;
|
|
char* sv = nullptr; // protect against -ENOMEM above
|
|
while (!!(arg = strtok_r(arg, delimiters, &sv))) {
|
|
if (!strcmp(arg, "default")) {
|
|
idMask |= (1 << LOG_ID_MAIN) | (1 << LOG_ID_SYSTEM) |
|
|
(1 << LOG_ID_CRASH);
|
|
} else if (!strcmp(arg, "all")) {
|
|
allSelected = true;
|
|
idMask = (unsigned)-1;
|
|
} else {
|
|
log_id_t log_id = android_name_to_log_id(arg);
|
|
const char* name = android_log_id_to_name(log_id);
|
|
|
|
if (!!strcmp(name, arg)) {
|
|
logcat_panic(context, HELP_TRUE,
|
|
"unknown buffer %s\n", arg);
|
|
goto exit;
|
|
}
|
|
if (log_id == LOG_ID_SECURITY) allSelected = false;
|
|
idMask |= (1 << log_id);
|
|
}
|
|
arg = nullptr;
|
|
}
|
|
|
|
for (int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
|
|
const char* name = android_log_id_to_name((log_id_t)i);
|
|
log_id_t log_id = android_name_to_log_id(name);
|
|
|
|
if (log_id != (log_id_t)i) continue;
|
|
if (!(idMask & (1 << i))) continue;
|
|
|
|
bool found = false;
|
|
for (dev = context->devices; dev; dev = dev->next) {
|
|
if (!strcmp(name, dev->device)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
if (!dev->next) break;
|
|
}
|
|
if (found) continue;
|
|
|
|
bool binary =
|
|
!strcmp(name, "events") || !strcmp(name, "security");
|
|
log_device_t* d = new log_device_t(name, binary);
|
|
|
|
if (dev) {
|
|
dev->next = d;
|
|
dev = d;
|
|
} else {
|
|
context->devices = dev = d;
|
|
}
|
|
context->devCount++;
|
|
}
|
|
} break;
|
|
|
|
case 'B':
|
|
context->printBinary = 1;
|
|
break;
|
|
|
|
case 'f':
|
|
if ((tail_time == log_time::EPOCH) && !tail_lines) {
|
|
tail_time = lastLogTime(optctx.optarg);
|
|
}
|
|
// redirect output to a file
|
|
context->outputFileName = optctx.optarg;
|
|
break;
|
|
|
|
case 'r':
|
|
if (!getSizeTArg(optctx.optarg, &context->logRotateSizeKBytes,
|
|
1)) {
|
|
logcat_panic(context, HELP_TRUE,
|
|
"Invalid parameter \"%s\" to -r\n",
|
|
optctx.optarg);
|
|
goto exit;
|
|
}
|
|
break;
|
|
|
|
case 'n':
|
|
if (!getSizeTArg(optctx.optarg, &context->maxRotatedLogs, 1)) {
|
|
logcat_panic(context, HELP_TRUE,
|
|
"Invalid parameter \"%s\" to -n\n",
|
|
optctx.optarg);
|
|
goto exit;
|
|
}
|
|
break;
|
|
|
|
case 'v': {
|
|
if (!strcmp(optctx.optarg, "help") ||
|
|
!strcmp(optctx.optarg, "--help")) {
|
|
show_format_help(context);
|
|
context->retval = EXIT_SUCCESS;
|
|
goto exit;
|
|
}
|
|
std::unique_ptr<char, void (*)(void*)> formats(
|
|
strdup(optctx.optarg), free);
|
|
char* arg = formats.get();
|
|
unsigned idMask = 0;
|
|
char* sv = nullptr; // protect against -ENOMEM above
|
|
while (!!(arg = strtok_r(arg, delimiters, &sv))) {
|
|
err = setLogFormat(context, arg);
|
|
if (err < 0) {
|
|
logcat_panic(context, HELP_FORMAT,
|
|
"Invalid parameter \"%s\" to -v\n", arg);
|
|
goto exit;
|
|
}
|
|
arg = nullptr;
|
|
if (err) hasSetLogFormat = true;
|
|
}
|
|
} break;
|
|
|
|
case 'Q':
|
|
#define LOGCAT_FILTER "androidboot.logcat="
|
|
#define CONSOLE_PIPE_OPTION "androidboot.consolepipe="
|
|
#define CONSOLE_OPTION "androidboot.console="
|
|
#define QEMU_PROPERTY "ro.kernel.qemu"
|
|
#define QEMU_CMDLINE "qemu.cmdline"
|
|
// This is a *hidden* option used to start a version of logcat
|
|
// in an emulated device only. It basically looks for
|
|
// androidboot.logcat= on the kernel command line. If
|
|
// something is found, it extracts a log filter and uses it to
|
|
// run the program. The logcat output will go to consolepipe if
|
|
// androiboot.consolepipe (e.g. qemu_pipe) is given, otherwise,
|
|
// it goes to androidboot.console (e.g. tty)
|
|
{
|
|
// if not in emulator, exit quietly
|
|
if (false == android::base::GetBoolProperty(QEMU_PROPERTY, false)) {
|
|
context->retval = EXIT_SUCCESS;
|
|
goto exit;
|
|
}
|
|
|
|
std::string cmdline = android::base::GetProperty(QEMU_CMDLINE, "");
|
|
if (cmdline.empty()) {
|
|
android::base::ReadFileToString("/proc/cmdline", &cmdline);
|
|
}
|
|
|
|
const char* logcatFilter = strstr(cmdline.c_str(), LOGCAT_FILTER);
|
|
// if nothing found or invalid filters, exit quietly
|
|
if (!logcatFilter) {
|
|
context->retval = EXIT_SUCCESS;
|
|
goto exit;
|
|
}
|
|
|
|
const char* p = logcatFilter + strlen(LOGCAT_FILTER);
|
|
const char* q = strpbrk(p, " \t\n\r");
|
|
if (!q) q = p + strlen(p);
|
|
forceFilters = std::string(p, q);
|
|
|
|
// redirect our output to the emulator console pipe or console
|
|
const char* consolePipe =
|
|
strstr(cmdline.c_str(), CONSOLE_PIPE_OPTION);
|
|
const char* console =
|
|
strstr(cmdline.c_str(), CONSOLE_OPTION);
|
|
|
|
if (consolePipe) {
|
|
p = consolePipe + strlen(CONSOLE_PIPE_OPTION);
|
|
} else if (console) {
|
|
p = console + strlen(CONSOLE_OPTION);
|
|
} else {
|
|
context->retval = EXIT_FAILURE;
|
|
goto exit;
|
|
}
|
|
|
|
q = strpbrk(p, " \t\n\r");
|
|
int len = q ? q - p : strlen(p);
|
|
std::string devname = "/dev/" + std::string(p, len);
|
|
std::string pipePurpose("pipe:logcat");
|
|
if (consolePipe) {
|
|
// example: "qemu_pipe,pipe:logcat"
|
|
// upon opening of /dev/qemu_pipe, the "pipe:logcat"
|
|
// string with trailing '\0' should be written to the fd
|
|
size_t pos = devname.find(",");
|
|
if (pos != std::string::npos) {
|
|
pipePurpose = devname.substr(pos + 1);
|
|
devname = devname.substr(0, pos);
|
|
}
|
|
}
|
|
cmdline.erase();
|
|
|
|
if (context->error) {
|
|
fprintf(context->error, "logcat using %s\n",
|
|
devname.c_str());
|
|
}
|
|
|
|
FILE* fp = fopen(devname.c_str(), "web");
|
|
devname.erase();
|
|
if (!fp) break;
|
|
|
|
if (consolePipe) {
|
|
// need the trailing '\0'
|
|
if(!android::base::WriteFully(fileno(fp), pipePurpose.c_str(),
|
|
pipePurpose.size() + 1)) {
|
|
fclose(fp);
|
|
context->retval = EXIT_FAILURE;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// close output and error channels, replace with console
|
|
android::close_output(context);
|
|
android::close_error(context);
|
|
context->stderr_stdout = true;
|
|
context->output = fp;
|
|
context->output_fd = fileno(fp);
|
|
if (context->stderr_null) break;
|
|
context->stderr_stdout = true;
|
|
context->error = fp;
|
|
context->error_fd = fileno(fp);
|
|
}
|
|
break;
|
|
|
|
case 'S':
|
|
printStatistics = true;
|
|
break;
|
|
|
|
case ':':
|
|
logcat_panic(context, HELP_TRUE,
|
|
"Option -%c needs an argument\n", optctx.optopt);
|
|
goto exit;
|
|
|
|
default:
|
|
logcat_panic(context, HELP_TRUE, "Unrecognized Option %c\n",
|
|
optctx.optopt);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
if (context->maxCount && got_t) {
|
|
logcat_panic(context, HELP_TRUE,
|
|
"Cannot use -m (--max-count) and -t together\n");
|
|
goto exit;
|
|
}
|
|
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
|
|
if (context->error) {
|
|
fprintf(context->error, "WARNING: "
|
|
"--print ignored, to be used in combination with\n"
|
|
" "
|
|
"--regex <expr> and --max-count <N>\n");
|
|
}
|
|
context->printItAnyways = false;
|
|
}
|
|
|
|
if (!context->devices) {
|
|
dev = context->devices = new log_device_t("main", false);
|
|
context->devCount = 1;
|
|
if (android_name_to_log_id("system") == LOG_ID_SYSTEM) {
|
|
dev = dev->next = new log_device_t("system", false);
|
|
context->devCount++;
|
|
}
|
|
if (android_name_to_log_id("crash") == LOG_ID_CRASH) {
|
|
dev = dev->next = new log_device_t("crash", false);
|
|
context->devCount++;
|
|
}
|
|
}
|
|
|
|
if (!!context->logRotateSizeKBytes && !context->outputFileName) {
|
|
logcat_panic(context, HELP_TRUE, "-r requires -f as well\n");
|
|
goto exit;
|
|
}
|
|
|
|
if (!!setId) {
|
|
if (!context->outputFileName) {
|
|
logcat_panic(context, HELP_TRUE,
|
|
"--id='%s' requires -f as well\n", setId);
|
|
goto exit;
|
|
}
|
|
|
|
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,
|
|
getuid(), getgid());
|
|
if (!file_ok || !file.compare(setId)) setId = nullptr;
|
|
}
|
|
|
|
if (!hasSetLogFormat) {
|
|
const char* logFormat = android::getenv(context, "ANDROID_PRINTF_LOG");
|
|
|
|
if (!!logFormat) {
|
|
std::unique_ptr<char, void (*)(void*)> formats(strdup(logFormat),
|
|
free);
|
|
char* sv = nullptr; // protect against -ENOMEM above
|
|
char* arg = formats.get();
|
|
while (!!(arg = strtok_r(arg, delimiters, &sv))) {
|
|
err = setLogFormat(context, arg);
|
|
// environment should not cause crash of logcat
|
|
if ((err < 0) && context->error) {
|
|
fprintf(context->error,
|
|
"invalid format in ANDROID_PRINTF_LOG '%s'\n", arg);
|
|
}
|
|
arg = nullptr;
|
|
if (err > 0) hasSetLogFormat = true;
|
|
}
|
|
}
|
|
if (!hasSetLogFormat) {
|
|
setLogFormat(context, "threadtime");
|
|
}
|
|
}
|
|
|
|
if (forceFilters.size()) {
|
|
err = android_log_addFilterString(context->logformat,
|
|
forceFilters.c_str());
|
|
if (err < 0) {
|
|
logcat_panic(context, HELP_FALSE,
|
|
"Invalid filter expression in logcat args\n");
|
|
goto exit;
|
|
}
|
|
} else if (argc == optctx.optind) {
|
|
// Add from environment variable
|
|
const char* env_tags_orig = android::getenv(context, "ANDROID_LOG_TAGS");
|
|
|
|
if (!!env_tags_orig) {
|
|
err = android_log_addFilterString(context->logformat,
|
|
env_tags_orig);
|
|
|
|
if (err < 0) {
|
|
logcat_panic(context, HELP_TRUE,
|
|
"Invalid filter expression in ANDROID_LOG_TAGS\n");
|
|
goto exit;
|
|
}
|
|
}
|
|
} else {
|
|
// Add from commandline
|
|
for (int i = optctx.optind ; i < argc ; i++) {
|
|
// skip stderr redirections of _all_ kinds
|
|
if ((argv[i][0] == '2') && (argv[i][1] == '>')) continue;
|
|
// skip stdout redirections of _all_ kinds
|
|
if (argv[i][0] == '>') continue;
|
|
|
|
err = android_log_addFilterString(context->logformat, argv[i]);
|
|
if (err < 0) {
|
|
logcat_panic(context, HELP_TRUE,
|
|
"Invalid filter expression '%s'\n", argv[i]);
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
dev = context->devices;
|
|
if (tail_time != log_time::EPOCH) {
|
|
logger_list = android_logger_list_alloc_time(mode, tail_time, pid);
|
|
} else {
|
|
logger_list = android_logger_list_alloc(mode, tail_lines, pid);
|
|
}
|
|
// We have three orthogonal actions below to clear, set log size and
|
|
// get log size. All sharing the same iteration loop.
|
|
while (dev) {
|
|
dev->logger_list = logger_list;
|
|
dev->logger = android_logger_open(logger_list,
|
|
android_name_to_log_id(dev->device));
|
|
if (!dev->logger) {
|
|
reportErrorName(&openDeviceFail, dev->device, allSelected);
|
|
dev = dev->next;
|
|
continue;
|
|
}
|
|
|
|
if (clearLog || setId) {
|
|
if (context->outputFileName) {
|
|
int maxRotationCountDigits =
|
|
(context->maxRotatedLogs > 0) ?
|
|
(int)(floor(log10(context->maxRotatedLogs) + 1)) :
|
|
0;
|
|
|
|
for (int i = context->maxRotatedLogs ; i >= 0 ; --i) {
|
|
std::string file;
|
|
|
|
if (!i) {
|
|
file = android::base::StringPrintf(
|
|
"%s", context->outputFileName);
|
|
} else {
|
|
file = android::base::StringPrintf("%s.%.*d",
|
|
context->outputFileName, maxRotationCountDigits, i);
|
|
}
|
|
|
|
if (!file.length()) {
|
|
perror("while clearing log files");
|
|
reportErrorName(&clearFail, dev->device, allSelected);
|
|
break;
|
|
}
|
|
|
|
err = unlink(file.c_str());
|
|
|
|
if (err < 0 && errno != ENOENT && !clearFail) {
|
|
perror("while clearing log files");
|
|
reportErrorName(&clearFail, dev->device, allSelected);
|
|
}
|
|
}
|
|
} else if (android_logger_clear(dev->logger)) {
|
|
reportErrorName(&clearFail, dev->device, allSelected);
|
|
}
|
|
}
|
|
|
|
if (setLogSize) {
|
|
if (android_logger_set_log_size(dev->logger, setLogSize)) {
|
|
reportErrorName(&setSizeFail, dev->device, allSelected);
|
|
}
|
|
}
|
|
|
|
if (getLogSize) {
|
|
long size = android_logger_get_log_size(dev->logger);
|
|
long readable = android_logger_get_log_readable_size(dev->logger);
|
|
|
|
if ((size < 0) || (readable < 0)) {
|
|
reportErrorName(&getSizeFail, dev->device, allSelected);
|
|
} else {
|
|
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(context, HELP_FALSE,
|
|
"Unable to open log device '%s'\n", openDeviceFail);
|
|
goto close;
|
|
}
|
|
if (clearFail) {
|
|
logcat_panic(context, HELP_FALSE,
|
|
"failed to clear the '%s' log\n", clearFail);
|
|
goto close;
|
|
}
|
|
if (setSizeFail) {
|
|
logcat_panic(context, HELP_FALSE,
|
|
"failed to set the '%s' log size\n", setSizeFail);
|
|
goto close;
|
|
}
|
|
if (getSizeFail) {
|
|
logcat_panic(context, HELP_FALSE,
|
|
"failed to get the readable '%s' log size", getSizeFail);
|
|
goto close;
|
|
}
|
|
|
|
if (setPruneList) {
|
|
size_t len = strlen(setPruneList);
|
|
// extra 32 bytes are needed by android_logger_set_prune_list
|
|
size_t bLen = len + 32;
|
|
char* buf = nullptr;
|
|
if (asprintf(&buf, "%-*s", (int)(bLen - 1), setPruneList) > 0) {
|
|
buf[len] = '\0';
|
|
if (android_logger_set_prune_list(logger_list, buf, bLen)) {
|
|
logcat_panic(context, HELP_FALSE,
|
|
"failed to set the prune list");
|
|
}
|
|
free(buf);
|
|
} else {
|
|
logcat_panic(context, HELP_FALSE,
|
|
"failed to set the prune list (alloc)");
|
|
}
|
|
goto close;
|
|
}
|
|
|
|
if (printStatistics || getPruneList) {
|
|
size_t len = 8192;
|
|
char* buf;
|
|
|
|
for (int retry = 32; (retry >= 0) && ((buf = new char[len]));
|
|
delete[] buf, buf = nullptr, --retry) {
|
|
if (getPruneList) {
|
|
android_logger_get_prune_list(logger_list, buf, len);
|
|
} else {
|
|
android_logger_get_statistics(logger_list, buf, len);
|
|
}
|
|
buf[len - 1] = '\0';
|
|
if (atol(buf) < 3) {
|
|
delete[] buf;
|
|
buf = nullptr;
|
|
break;
|
|
}
|
|
size_t ret = atol(buf) + 1;
|
|
if (ret <= len) {
|
|
len = ret;
|
|
break;
|
|
}
|
|
len = ret;
|
|
}
|
|
|
|
if (!buf) {
|
|
logcat_panic(context, HELP_FALSE, "failed to read data");
|
|
goto close;
|
|
}
|
|
|
|
// remove trailing FF
|
|
char* cp = buf + len - 1;
|
|
*cp = '\0';
|
|
bool truncated = *--cp != '\f';
|
|
if (!truncated) *cp = '\0';
|
|
|
|
// squash out the byte count
|
|
cp = buf;
|
|
if (!truncated) {
|
|
while (isdigit(*cp)) ++cp;
|
|
if (*cp == '\n') ++cp;
|
|
}
|
|
|
|
len = strlen(cp);
|
|
TEMP_FAILURE_RETRY(write(context->output_fd, cp, len));
|
|
delete[] buf;
|
|
goto close;
|
|
}
|
|
|
|
if (getLogSize || setLogSize || clearLog) goto close;
|
|
|
|
setupOutputAndSchedulingPolicy(context, !(mode & ANDROID_LOG_NONBLOCK));
|
|
if (context->stop) goto close;
|
|
|
|
// LOG_EVENT_INT(10, 12345);
|
|
// LOG_EVENT_LONG(11, 0x1122334455667788LL);
|
|
// LOG_EVENT_STRING(0, "whassup, doc?");
|
|
|
|
dev = nullptr;
|
|
|
|
while (!context->stop &&
|
|
(!context->maxCount || (context->printCount < context->maxCount))) {
|
|
struct log_msg log_msg;
|
|
int ret = android_logger_list_read(logger_list, &log_msg);
|
|
if (!ret) {
|
|
logcat_panic(context, HELP_FALSE, "read: unexpected EOF!\n");
|
|
break;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
if (ret == -EAGAIN) break;
|
|
|
|
if (ret == -EIO) {
|
|
logcat_panic(context, HELP_FALSE, "read: unexpected EOF!\n");
|
|
break;
|
|
}
|
|
if (ret == -EINVAL) {
|
|
logcat_panic(context, HELP_FALSE, "read: unexpected length.\n");
|
|
break;
|
|
}
|
|
logcat_panic(context, HELP_FALSE, "logcat read failure");
|
|
break;
|
|
}
|
|
|
|
log_device_t* d;
|
|
for (d = context->devices; d; d = d->next) {
|
|
if (android_name_to_log_id(d->device) == log_msg.id()) break;
|
|
}
|
|
if (!d) {
|
|
context->devCount = 2; // set to Multiple
|
|
d = &unexpected;
|
|
d->binary = log_msg.id() == LOG_ID_EVENTS;
|
|
}
|
|
|
|
if (dev != d) {
|
|
dev = d;
|
|
maybePrintStart(context, dev, printDividers);
|
|
if (context->stop) break;
|
|
}
|
|
if (context->printBinary) {
|
|
printBinary(context, &log_msg);
|
|
} else {
|
|
processBuffer(context, dev, &log_msg);
|
|
}
|
|
}
|
|
|
|
close:
|
|
// Short and sweet. Implemented generic version in android_logcat_destroy.
|
|
while (!!(dev = context->devices)) {
|
|
context->devices = dev->next;
|
|
delete dev;
|
|
}
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
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(¶m, 0, sizeof(param));
|
|
pthread_attr_setschedparam(&attr, ¶m);
|
|
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(nullptr);
|
|
for (auto& str : context->envs) {
|
|
context->envp_hold.push_back(str.c_str());
|
|
}
|
|
context->envp_hold.push_back(nullptr);
|
|
|
|
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 ?: "nullptr");
|
|
}
|
|
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;
|
|
|
|
if (!context) return -EBADF;
|
|
|
|
*ctx = nullptr;
|
|
|
|
context->stop = true;
|
|
|
|
while (context->thread_stopped == false) {
|
|
// Makes me sad, replace thread_stopped with semaphore. Short lived.
|
|
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);
|
|
|
|
// generic cleanup of devices list to handle all possible dirty cases
|
|
log_device_t* dev;
|
|
while (!!(dev = context->devices)) {
|
|
struct logger_list* logger_list = dev->logger_list;
|
|
if (logger_list) {
|
|
for (log_device_t* d = dev; d; d = d->next) {
|
|
if (d->logger_list == logger_list) d->logger_list = nullptr;
|
|
}
|
|
android_logger_list_free(logger_list);
|
|
}
|
|
context->devices = dev->next;
|
|
delete dev;
|
|
}
|
|
|
|
int retval = context->retval;
|
|
|
|
free(context);
|
|
|
|
return retval;
|
|
}
|