liblogcat: -v and ${ANDROID_PRINTF_LOG} support comma separated list

Deal with yet another reentrancy issue with strtok() and do not
treat optarg as fungible data, allocate a temporary copy.  Add
support in the -v flag and environmental variable ANDROID_PRINTF_LOG
for a comma-separated list of format and format modifiers.

Test: manual + gTest logcat-unit-tests
Bug: 35326290
Change-Id: I073e5676d59ba41bfd36df86942342766ef730f1
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 3ce0db3..077332a 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -446,6 +446,8 @@
                     "                  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"
@@ -726,7 +728,7 @@
 static int __logcat(android_logcat_context_internal* context) {
     using namespace android;
     int err;
-    int hasSetLogFormat = 0;
+    bool hasSetLogFormat = false;
     bool clearLog = false;
     bool allSelected = false;
     bool getLogSize = false;
@@ -849,6 +851,10 @@
         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";
+
     // danger: getopt is _not_ reentrant
     optind = 1;
     for (;;) {
@@ -1070,8 +1076,12 @@
                 break;
 
             case 'b': {
+                std::unique_ptr<char, void (*)(void*)> buffers(strdup(optarg),
+                                                               free);
+                optarg = buffers.get();
                 unsigned idMask = 0;
-                while (!!(optarg = strtok(optarg, ",:; \t\n\r\f"))) {
+                char* sv = nullptr;  // protect against -ENOMEM above
+                while (!!(optarg = strtok_r(optarg, delimiters, &sv))) {
                     if (!strcmp(optarg, "default")) {
                         idMask |= (1 << LOG_ID_MAIN) | (1 << LOG_ID_SYSTEM) |
                                   (1 << LOG_ID_CRASH);
@@ -1152,20 +1162,28 @@
                 }
                 break;
 
-            case 'v':
+            case 'v': {
                 if (!strcmp(optarg, "help") || !strcmp(optarg, "--help")) {
                     show_format_help(context);
                     context->retval = EXIT_SUCCESS;
                     goto exit;
                 }
-                err = setLogFormat(context, optarg);
-                if (err < 0) {
-                    logcat_panic(context, HELP_FORMAT,
-                                 "Invalid parameter \"%s\" to -v\n", optarg);
-                    goto exit;
+                std::unique_ptr<char, void (*)(void*)> formats(strdup(optarg),
+                                                               free);
+                optarg = formats.get();
+                unsigned idMask = 0;
+                char* sv = nullptr;  // protect against -ENOMEM above
+                while (!!(optarg = strtok_r(optarg, delimiters, &sv))) {
+                    err = setLogFormat(context, optarg);
+                    if (err < 0) {
+                        logcat_panic(context, HELP_FORMAT,
+                                     "Invalid parameter \"%s\" to -v\n", optarg);
+                        goto exit;
+                    }
+                    optarg = nullptr;
+                    if (err) hasSetLogFormat = true;
                 }
-                hasSetLogFormat |= err;
-                break;
+            } break;
 
             case 'Q':
 #define KERNEL_OPTION "androidboot.logcat="
@@ -1297,13 +1315,22 @@
         const char* logFormat = android::getenv(context, "ANDROID_PRINTF_LOG");
 
         if (!!logFormat) {
-            err = setLogFormat(context, logFormat);
-            if ((err < 0) && context->error) {
-                fprintf(context->error,
-                        "invalid format in ANDROID_PRINTF_LOG '%s'\n",
-                        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;
             }
-        } else {
+        }
+        if (!hasSetLogFormat) {
             setLogFormat(context, "threadtime");
         }
     }