logcat: allow comma-separate list of buffers

(cherry pick from commit 45177732966d82a9ee8d558f9b115e061e0c7208)

- Add parsing to support comma+ separated list of buffers.
- Move get_size test into a standalone get_groups function
- add some additional tests for this feature, leveraging
  get_groups to confirm buffer selection.

Bug: 28120456
Bug: 28788401
Bug: 28813587
Change-Id: I0b42736c08cf4b2a435cb74cda540dc163a26bd1
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 697d725..33f22dd 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -316,8 +316,8 @@
                     // 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 are allowed. Buffers interleaved.\n"
-                    "                  Default -b main -b system -b crash.\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"
@@ -763,111 +763,63 @@
             break;
 
             case 'b': {
-                if (strcmp(optarg, "default") == 0) {
-                    for (int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
-                        switch (i) {
-                        case LOG_ID_SECURITY:
-                        case LOG_ID_EVENTS:
-                            continue;
-                        case LOG_ID_MAIN:
-                        case LOG_ID_SYSTEM:
-                        case LOG_ID_CRASH:
-                            break;
-                        default:
-                            continue;
-                        }
+                unsigned idMask = 0;
+                while ((optarg = strtok(optarg, ",:; \t\n\r\f")) != NULL) {
+                    if (strcmp(optarg, "default") == 0) {
+                        idMask |= (1 << LOG_ID_MAIN) |
+                                  (1 << LOG_ID_SYSTEM) |
+                                  (1 << LOG_ID_CRASH);
+                    } else if (strcmp(optarg, "all") == 0) {
+                        idMask = (unsigned)-1;
+                    } else {
+                        log_id_t log_id = android_name_to_log_id(optarg);
+                        const char *name = android_log_id_to_name(log_id);
 
-                        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 (strcmp(name, optarg) != 0) {
+                            logcat_panic(true, "unknown buffer %s\n", optarg);
                         }
-
-                        bool found = false;
-                        for (dev = devices; dev; dev = dev->next) {
-                            if (!strcmp(optarg, dev->device)) {
-                                found = true;
-                                break;
-                            }
-                            if (!dev->next) {
-                                break;
-                            }
-                        }
-                        if (found) {
-                            break;
-                        }
-
-                        log_device_t* d = new log_device_t(name, false);
-
-                        if (dev) {
-                            dev->next = d;
-                            dev = d;
-                        } else {
-                            devices = dev = d;
-                        }
-                        g_devCount++;
+                        idMask |= (1 << log_id);
                     }
-                    break;
+                    optarg = NULL;
                 }
 
-                if (strcmp(optarg, "all") == 0) {
-                    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);
+                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 (log_id != (log_id_t)i) {
+                        continue;
+                    }
+                    if ((idMask & (1 << i)) == 0) {
+                        continue;
+                    }
 
-                        bool found = false;
-                        for (dev = devices; dev; dev = dev->next) {
-                            if (!strcmp(optarg, dev->device)) {
-                                found = true;
-                                break;
-                            }
-                            if (!dev->next) {
-                                break;
-                            }
-                        }
-                        if (found) {
+                    bool found = false;
+                    for (dev = devices; dev; dev = dev->next) {
+                        if (!strcmp(name, dev->device)) {
+                            found = true;
                             break;
                         }
-
-                        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 {
-                            devices = dev = d;
-                        }
-                        g_devCount++;
-                    }
-                    break;
-                }
-
-                bool binary = !(strcmp(optarg, "events") &&
-                                strcmp(optarg, "security"));
-
-                if (devices) {
-                    dev = devices;
-                    while (dev->next) {
-                        if (!strcmp(optarg, dev->device)) {
-                            dev = NULL;
+                        if (!dev->next) {
                             break;
                         }
-                        dev = dev->next;
                     }
+                    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 = new log_device_t(optarg, binary);
+                        dev->next = d;
+                        dev = d;
+                    } else {
+                        devices = dev = d;
                     }
-                } else {
-                    devices = new log_device_t(optarg, binary);
+                    g_devCount++;
                 }
-                g_devCount++;
             }
             break;
 
diff --git a/logcat/tests/logcat_test.cpp b/logcat/tests/logcat_test.cpp
index f61db69..d79fc08 100644
--- a/logcat/tests/logcat_test.cpp
+++ b/logcat/tests/logcat_test.cpp
@@ -28,6 +28,8 @@
 #include <log/logger.h>
 #include <log/log_read.h>
 
+#define BIG_BUFFER (5 * 1024)
+
 // enhanced version of LOG_FAILURE_RETRY to add support for EAGAIN and
 // non-syscall libs. Since we are only using this in the emergency of
 // a signal to stuff a terminating code into the logs, we will spin rather
@@ -52,7 +54,7 @@
       "logcat -b radio -b events -b system -b main -d 2>/dev/null",
       "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int ids = 0;
     int count = 0;
@@ -100,7 +102,7 @@
       "logcat -v long -v year -b all -t 3 2>/dev/null",
       "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -163,7 +165,7 @@
           "logcat -v long -v America/Los_Angeles -b all -t 3 2>/dev/null",
           "r")));
 
-        char buffer[5120];
+        char buffer[BIG_BUFFER];
 
         count = 0;
 
@@ -187,7 +189,7 @@
       "logcat -v long -v America/Los_Angeles -v zone -b all -t 3 2>/dev/null",
       "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -207,7 +209,7 @@
     int count;
 
     do {
-        char buffer[5120];
+        char buffer[BIG_BUFFER];
 
         snprintf(buffer, sizeof(buffer),
           "logcat -v long -b radio -b events -b system -b main -t %d 2>/dev/null",
@@ -250,7 +252,7 @@
 
     ASSERT_TRUE(NULL != (fp = popen("logcat -v long -b all -t 10 2>&1", "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
     char *last_timestamp = NULL;
     char *first_timestamp = NULL;
     int count = 0;
@@ -313,7 +315,7 @@
       "logcat -v brief -b events -t 100 2>/dev/null",
       "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -337,15 +339,17 @@
     ASSERT_EQ(1, count);
 }
 
-TEST(logcat, get_size) {
+int get_groups(const char *cmd) {
     FILE *fp;
 
     // NB: crash log only available in user space
-    ASSERT_TRUE(NULL != (fp = popen(
-      "logcat -v brief -b radio -b events -b system -b main -g 2>/dev/null",
-      "r")));
+    EXPECT_TRUE(NULL != (fp = popen(cmd, "r")));
 
-    char buffer[5120];
+    if (fp == NULL) {
+        return 0;
+    }
+
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -405,7 +409,23 @@
 
     pclose(fp);
 
-    ASSERT_EQ(4, count);
+    return count;
+}
+
+TEST(logcat, get_size) {
+    ASSERT_EQ(4, get_groups(
+      "logcat -v brief -b radio -b events -b system -b main -g 2>/dev/null"));
+}
+
+// duplicate test for get_size, but use comma-separated list of buffers
+TEST(logcat, multiple_buffer) {
+    ASSERT_EQ(4, get_groups(
+      "logcat -v brief -b radio,events,system,main -g 2>/dev/null"));
+}
+
+TEST(logcat, bad_buffer) {
+    ASSERT_EQ(0, get_groups(
+      "logcat -v brief -b radio,events,bogo,system,main -g 2>/dev/null"));
 }
 
 static void caught_blocking(int /*signum*/)
@@ -434,7 +454,7 @@
       " logcat -v brief -b events 2>&1",
       "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -503,7 +523,7 @@
       " logcat -v brief -b events -T 5 2>&1",
       "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -566,7 +586,7 @@
         FILE *fp;
         EXPECT_TRUE(NULL != (fp = popen(command, "r")));
         if (fp) {
-            char buffer[5120];
+            char buffer[BIG_BUFFER];
             int count = 0;
 
             while (fgets(buffer, sizeof(buffer), fp)) {
@@ -609,7 +629,7 @@
 
         FILE *fp;
         EXPECT_TRUE(NULL != (fp = popen(command, "r")));
-        char buffer[5120];
+        char buffer[BIG_BUFFER];
         int log_file_count = 0;
 
         while (fgets(buffer, sizeof(buffer), fp)) {
@@ -859,7 +879,7 @@
       " logcat -v brief -b events 2>&1",
       "r")));
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     int count = 0;
 
@@ -920,7 +940,7 @@
         return false;
     }
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     while (fgets(buffer, sizeof(buffer), fp)) {
         char *hold = *list;
@@ -949,7 +969,7 @@
 static bool set_white_black(const char *list) {
     FILE *fp;
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     snprintf(buffer, sizeof(buffer), "logcat -P '%s' 2>&1", list ? list : "");
     fp = popen(buffer, "r");
@@ -1011,7 +1031,7 @@
     FILE *fp;
     int count = 0;
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     snprintf(buffer, sizeof(buffer), "logcat --pid %d -d -e logcat_test_a+b", getpid());
 
@@ -1044,7 +1064,7 @@
     FILE *fp;
     int count = 0;
 
-    char buffer[5120];
+    char buffer[BIG_BUFFER];
 
     snprintf(buffer, sizeof(buffer), "logcat --pid %d -d --max-count 3", getpid());