/*
 * Copyright (C) 2013-2014 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 <ctype.h>
#include <dirent.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <memory>

#include <gtest/gtest.h>
#include <log/log.h>
#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
// than try a usleep.
#define LOG_FAILURE_RETRY(exp) ({  \
    typeof (exp) _rc;              \
    do {                           \
        _rc = (exp);               \
    } while (((_rc == -1)          \
           && ((errno == EINTR)    \
            || (errno == EAGAIN))) \
          || (_rc == -EINTR)       \
          || (_rc == -EAGAIN));    \
    _rc; })

static const char begin[] = "--------- beginning of ";

TEST(logcat, buckets) {
    FILE *fp;

    ASSERT_TRUE(NULL != (fp = popen(
      "logcat -b radio -b events -b system -b main -d 2>/dev/null",
      "r")));

    char buffer[BIG_BUFFER];

    int ids = 0;
    int count = 0;

    while (fgets(buffer, sizeof(buffer), fp)) {
        if (!strncmp(begin, buffer, sizeof(begin) - 1)) {
            while (char *cp = strrchr(buffer, '\n')) {
                *cp = '\0';
            }
            log_id_t id = android_name_to_log_id(buffer + sizeof(begin) - 1);
            ids |= 1 << id;
            ++count;
        }
    }

    pclose(fp);

    EXPECT_EQ(15, ids);

    EXPECT_EQ(4, count);
}

TEST(logcat, year) {

    if (android_log_clockid() == CLOCK_MONOTONIC) {
        fprintf(stderr, "Skipping test, logd is monotonic time\n");
        return;
    }

    FILE *fp;

    char needle[32];
    time_t now;
    time(&now);
    struct tm *ptm;
#if !defined(_WIN32)
    struct tm tmBuf;
    ptm = localtime_r(&now, &tmBuf);
#else
    ptm = localtime(&&now);
#endif
    strftime(needle, sizeof(needle), "[ %Y-", ptm);

    ASSERT_TRUE(NULL != (fp = popen(
      "logcat -v long -v year -b all -t 3 2>/dev/null",
      "r")));

    char buffer[BIG_BUFFER];

    int count = 0;

    while (fgets(buffer, sizeof(buffer), fp)) {
        if (!strncmp(buffer, needle, strlen(needle))) {
            ++count;
        }
    }

    pclose(fp);

    ASSERT_EQ(3, count);
}

// Return a pointer to each null terminated -v long time field.
char *fgetLongTime(char *buffer, size_t buflen, FILE *fp) {
    while (fgets(buffer, buflen, fp)) {
        char *cp = buffer;
        if (*cp != '[') {
            continue;
        }
        while (*++cp == ' ') {
            ;
        }
        char *ep = cp;
        while (isdigit(*ep)) {
            ++ep;
        }
        if ((*ep != '-') && (*ep != '.')) {
           continue;
        }
        // Find PID field
        while (((ep = strchr(ep, ':'))) && (*++ep != ' ')) {
            ;
        }
        if (!ep) {
            continue;
        }
        ep -= 7;
        *ep = '\0';
        return cp;
    }
    return NULL;
}

TEST(logcat, tz) {

    if (android_log_clockid() == CLOCK_MONOTONIC) {
        fprintf(stderr, "Skipping test, logd is monotonic time\n");
        return;
    }

    int tries = 3; // in case run too soon after system start or buffer clear
    int count;

    do {
        FILE *fp;

        ASSERT_TRUE(NULL != (fp = popen(
          "logcat -v long -v America/Los_Angeles -b all -t 3 2>/dev/null",
          "r")));

        char buffer[BIG_BUFFER];

        count = 0;

        while (fgetLongTime(buffer, sizeof(buffer), fp)) {
            if (strstr(buffer, " -0700") || strstr(buffer, " -0800")) {
                ++count;
            }
        }

        pclose(fp);

    } while ((count < 3) && --tries && (sleep(1), true));

    ASSERT_EQ(3, count);
}

TEST(logcat, ntz) {
    FILE *fp;

    ASSERT_TRUE(NULL != (fp = popen(
      "logcat -v long -v America/Los_Angeles -v zone -b all -t 3 2>/dev/null",
      "r")));

    char buffer[BIG_BUFFER];

    int count = 0;

    while (fgetLongTime(buffer, sizeof(buffer), fp)) {
        if (strstr(buffer, " -0700") || strstr(buffer, " -0800")) {
            ++count;
        }
    }

    pclose(fp);

    ASSERT_EQ(0, count);
}

void do_tail(int num) {
    int tries = 3; // in case run too soon after system start or buffer clear
    int count;

    do {
        char buffer[BIG_BUFFER];

        snprintf(buffer, sizeof(buffer),
          "logcat -v long -b radio -b events -b system -b main -t %d 2>/dev/null",
          num);

        FILE *fp;
        ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));

        count = 0;

        while (fgetLongTime(buffer, sizeof(buffer), fp)) {
            ++count;
        }

        pclose(fp);

    } while ((count < num) && --tries && (sleep(1), true));

    ASSERT_EQ(num, count);
}

TEST(logcat, tail_3) {
    do_tail(3);
}

TEST(logcat, tail_10) {
    do_tail(10);
}

TEST(logcat, tail_100) {
    do_tail(100);
}

TEST(logcat, tail_1000) {
    do_tail(1000);
}

TEST(logcat, tail_time) {
    FILE *fp;

    ASSERT_TRUE(NULL != (fp = popen("logcat -v long -b all -t 10 2>&1", "r")));

    char buffer[BIG_BUFFER];
    char *last_timestamp = NULL;
    char *first_timestamp = NULL;
    int count = 0;

    char *cp;
    while ((cp = fgetLongTime(buffer, sizeof(buffer), fp))) {
        ++count;
        if (!first_timestamp) {
            first_timestamp = strdup(cp);
        }
        free(last_timestamp);
        last_timestamp = strdup(cp);
    }
    pclose(fp);

    EXPECT_EQ(10, count);
    EXPECT_TRUE(last_timestamp != NULL);
    EXPECT_TRUE(first_timestamp != NULL);

    snprintf(buffer, sizeof(buffer), "logcat -v long -b all -t '%s' 2>&1",
             first_timestamp);
    ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));

    int second_count = 0;
    int last_timestamp_count = -1;

    while ((cp = fgetLongTime(buffer, sizeof(buffer), fp))) {
        ++second_count;
        if (first_timestamp) {
            // we can get a transitory *extremely* rare failure if hidden
            // underneath the time is *exactly* XX-XX XX:XX:XX.XXX000000
            EXPECT_STREQ(cp, first_timestamp);
            free(first_timestamp);
            first_timestamp = NULL;
        }
        if (!strcmp(cp, last_timestamp)) {
            last_timestamp_count = second_count;
        }
    }
    pclose(fp);

    free(last_timestamp);
    last_timestamp = NULL;
    free(first_timestamp);

    EXPECT_TRUE(first_timestamp == NULL);
    EXPECT_LE(count, second_count);
    EXPECT_LE(count, last_timestamp_count);
}

TEST(logcat, End_to_End) {
    pid_t pid = getpid();

    log_time ts(CLOCK_MONOTONIC);

    ASSERT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));

    FILE *fp;
    ASSERT_TRUE(NULL != (fp = popen(
      "logcat -v brief -b events -t 100 2>/dev/null",
      "r")));

    char buffer[BIG_BUFFER];

    int count = 0;

    while (fgets(buffer, sizeof(buffer), fp)) {
        int p;
        unsigned long long t;

        if ((2 != sscanf(buffer, "I/[0]     ( %d): %llu", &p, &t))
         || (p != pid)) {
            continue;
        }

        log_time tx((const char *) &t);
        if (ts == tx) {
            ++count;
        }
    }

    pclose(fp);

    ASSERT_EQ(1, count);
}

int get_groups(const char *cmd) {
    FILE *fp;

    // NB: crash log only available in user space
    EXPECT_TRUE(NULL != (fp = popen(cmd, "r")));

    if (fp == NULL) {
        return 0;
    }

    char buffer[BIG_BUFFER];

    int count = 0;

    while (fgets(buffer, sizeof(buffer), fp)) {
        int size, consumed, max, payload;
        char size_mult[3], consumed_mult[3];
        long full_size, full_consumed;

        size = consumed = max = payload = 0;
        // NB: crash log can be very small, not hit a Kb of consumed space
        //     doubly lucky we are not including it.
        if (6 != sscanf(buffer, "%*s ring buffer is %d%2s (%d%2s consumed),"
                                " max entry is %db, max payload is %db",
                                &size, size_mult, &consumed, consumed_mult,
                                &max, &payload)) {
            fprintf(stderr, "WARNING: Parse error: %s", buffer);
            continue;
        }
        full_size = size;
        switch(size_mult[0]) {
        case 'G':
            full_size *= 1024;
            /* FALLTHRU */
        case 'M':
            full_size *= 1024;
            /* FALLTHRU */
        case 'K':
            full_size *= 1024;
            /* FALLTHRU */
        case 'b':
            break;
        }
        full_consumed = consumed;
        switch(consumed_mult[0]) {
        case 'G':
            full_consumed *= 1024;
            /* FALLTHRU */
        case 'M':
            full_consumed *= 1024;
            /* FALLTHRU */
        case 'K':
            full_consumed *= 1024;
            /* FALLTHRU */
        case 'b':
            break;
        }
        EXPECT_GT((full_size * 9) / 4, full_consumed);
        EXPECT_GT(full_size, max);
        EXPECT_GT(max, payload);

        if ((((full_size * 9) / 4) >= full_consumed)
         && (full_size > max)
         && (max > payload)) {
            ++count;
        }
    }

    pclose(fp);

    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*/)
{
    unsigned long long v = 0xDEADBEEFA55A0000ULL;

    v += getpid() & 0xFFFF;

    LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
}

TEST(logcat, blocking) {
    FILE *fp;
    unsigned long long v = 0xDEADBEEFA55F0000ULL;

    pid_t pid = getpid();

    v += pid & 0xFFFF;

    LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));

    v &= 0xFFFFFFFFFFFAFFFFULL;

    ASSERT_TRUE(NULL != (fp = popen(
      "( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&"
      " logcat -v brief -b events 2>&1",
      "r")));

    char buffer[BIG_BUFFER];

    int count = 0;

    int signals = 0;

    signal(SIGALRM, caught_blocking);
    alarm(2);
    while (fgets(buffer, sizeof(buffer), fp)) {

        if (!strncmp(buffer, "DONE", 4)) {
            break;
        }

        ++count;

        int p;
        unsigned long long l;

        if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l))
         || (p != pid)) {
            continue;
        }

        if (l == v) {
            ++signals;
            break;
        }
    }
    alarm(0);
    signal(SIGALRM, SIG_DFL);

    // Generate SIGPIPE
    fclose(fp);
    caught_blocking(0);

    pclose(fp);

    EXPECT_LE(2, count);

    EXPECT_EQ(1, signals);
}

static void caught_blocking_tail(int /*signum*/)
{
    unsigned long long v = 0xA55ADEADBEEF0000ULL;

    v += getpid() & 0xFFFF;

    LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
}

TEST(logcat, blocking_tail) {
    FILE *fp;
    unsigned long long v = 0xA55FDEADBEEF0000ULL;

    pid_t pid = getpid();

    v += pid & 0xFFFF;

    LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));

    v &= 0xFFFAFFFFFFFFFFFFULL;

    ASSERT_TRUE(NULL != (fp = popen(
      "( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&"
      " logcat -v brief -b events -T 5 2>&1",
      "r")));

    char buffer[BIG_BUFFER];

    int count = 0;

    int signals = 0;

    signal(SIGALRM, caught_blocking_tail);
    alarm(2);
    while (fgets(buffer, sizeof(buffer), fp)) {

        if (!strncmp(buffer, "DONE", 4)) {
            break;
        }

        ++count;

        int p;
        unsigned long long l;

        if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l))
         || (p != pid)) {
            continue;
        }

        if (l == v) {
            if (count >= 5) {
                ++signals;
            }
            break;
        }
    }
    alarm(0);
    signal(SIGALRM, SIG_DFL);

    // Generate SIGPIPE
    fclose(fp);
    caught_blocking_tail(0);

    pclose(fp);

    EXPECT_LE(2, count);

    EXPECT_EQ(1, signals);
}

TEST(logcat, logrotate) {
    static const char form[] = "/data/local/tmp/logcat.logrotate.XXXXXX";
    char buf[sizeof(form)];
    ASSERT_TRUE(NULL != mkdtemp(strcpy(buf, form)));

    static const char comm[] = "logcat -b radio -b events -b system -b main"
                                     " -d -f %s/log.txt -n 7 -r 1";
    char command[sizeof(buf) + sizeof(comm)];
    snprintf(command, sizeof(command), comm, buf);

    int ret;
    EXPECT_FALSE((ret = system(command)));
    if (!ret) {
        snprintf(command, sizeof(command), "ls -s %s 2>/dev/null", buf);

        FILE *fp;
        EXPECT_TRUE(NULL != (fp = popen(command, "r")));
        if (fp) {
            char buffer[BIG_BUFFER];
            int count = 0;

            while (fgets(buffer, sizeof(buffer), fp)) {
                static const char total[] = "total ";
                int num;
                char c;

                if ((2 == sscanf(buffer, "%d log.tx%c", &num, &c)) &&
                        (num <= 40)) {
                    ++count;
                } else if (strncmp(buffer, total, sizeof(total) - 1)) {
                    fprintf(stderr, "WARNING: Parse error: %s", buffer);
                }
            }
            pclose(fp);
            if ((count != 7) && (count != 8)) {
                fprintf(stderr, "count=%d\n", count);
            }
            EXPECT_TRUE(count == 7 || count == 8);
        }
    }
    snprintf(command, sizeof(command), "rm -rf %s", buf);
    EXPECT_FALSE(system(command));
}

TEST(logcat, logrotate_suffix) {
    static const char tmp_out_dir_form[] = "/data/local/tmp/logcat.logrotate.XXXXXX";
    char tmp_out_dir[sizeof(tmp_out_dir_form)];
    ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form)));

    static const char logcat_cmd[] = "logcat -b radio -b events -b system -b main"
                                     " -d -f %s/log.txt -n 10 -r 1";
    char command[sizeof(tmp_out_dir) + sizeof(logcat_cmd)];
    snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir);

    int ret;
    EXPECT_FALSE((ret = system(command)));
    if (!ret) {
        snprintf(command, sizeof(command), "ls %s 2>/dev/null", tmp_out_dir);

        FILE *fp;
        EXPECT_TRUE(NULL != (fp = popen(command, "r")));
        char buffer[BIG_BUFFER];
        int log_file_count = 0;

        while (fgets(buffer, sizeof(buffer), fp)) {
            static const char rotated_log_filename_prefix[] = "log.txt.";
            static const size_t rotated_log_filename_prefix_len =
                strlen(rotated_log_filename_prefix);
            static const char log_filename[] = "log.txt";

            if (!strncmp(buffer, rotated_log_filename_prefix, rotated_log_filename_prefix_len)) {
              // Rotated file should have form log.txt.##
              char* rotated_log_filename_suffix = buffer + rotated_log_filename_prefix_len;
              char* endptr;
              const long int suffix_value = strtol(rotated_log_filename_suffix, &endptr, 10);
              EXPECT_EQ(rotated_log_filename_suffix + 2, endptr);
              EXPECT_LE(suffix_value, 10);
              EXPECT_GT(suffix_value, 0);
              ++log_file_count;
              continue;
            }

            if (!strncmp(buffer, log_filename, strlen(log_filename))) {
              ++log_file_count;
              continue;
            }

            fprintf(stderr, "ERROR: Unexpected file: %s", buffer);
            ADD_FAILURE();
        }
        pclose(fp);
        EXPECT_EQ(11, log_file_count);
    }
    snprintf(command, sizeof(command), "rm -rf %s", tmp_out_dir);
    EXPECT_FALSE(system(command));
}

TEST(logcat, logrotate_continue) {
    static const char tmp_out_dir_form[] = "/data/local/tmp/logcat.logrotate.XXXXXX";
    char tmp_out_dir[sizeof(tmp_out_dir_form)];
    ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form)));

    static const char log_filename[] = "log.txt";
    static const char logcat_cmd[] = "logcat -b all -d -f %s/%s -n 256 -r 1024";
    static const char cleanup_cmd[] = "rm -rf %s";
    char command[sizeof(tmp_out_dir) + sizeof(logcat_cmd) + sizeof(log_filename)];
    snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir, log_filename);

    int ret;
    EXPECT_FALSE((ret = system(command)));
    if (ret) {
        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
        EXPECT_FALSE(system(command));
        return;
    }
    FILE *fp;
    snprintf(command, sizeof(command), "%s/%s", tmp_out_dir, log_filename);
    EXPECT_TRUE(NULL != ((fp = fopen(command, "r"))));
    if (!fp) {
        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
        EXPECT_FALSE(system(command));
        return;
    }
    char *line = NULL;
    char *last_line = NULL; // this line is allowed to stutter, one-line overlap
    char *second_last_line = NULL;
    size_t len = 0;
    while (getline(&line, &len, fp) != -1) {
        free(second_last_line);
        second_last_line = last_line;
        last_line = line;
        line = NULL;
    }
    fclose(fp);
    free(line);
    if (second_last_line == NULL) {
        fprintf(stderr, "No second to last line, using last, test may fail\n");
        second_last_line = last_line;
        last_line = NULL;
    }
    free(last_line);
    EXPECT_TRUE(NULL != second_last_line);
    if (!second_last_line) {
        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
        EXPECT_FALSE(system(command));
        return;
    }
    // re-run the command, it should only add a few lines more content if it
    // continues where it left off.
    snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir, log_filename);
    EXPECT_FALSE((ret = system(command)));
    if (ret) {
        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
        EXPECT_FALSE(system(command));
        return;
    }
    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir), closedir);
    EXPECT_NE(nullptr, dir);
    if (!dir) {
        snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
        EXPECT_FALSE(system(command));
        return;
    }
    struct dirent *entry;
    unsigned count = 0;
    while ((entry = readdir(dir.get()))) {
        if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) {
            continue;
        }
        snprintf(command, sizeof(command), "%s/%s", tmp_out_dir, entry->d_name);
        EXPECT_TRUE(NULL != ((fp = fopen(command, "r"))));
        if (!fp) {
            fprintf(stderr, "%s ?\n", command);
            continue;
        }
        line = NULL;
        size_t number = 0;
        while (getline(&line, &len, fp) != -1) {
            ++number;
            if (!strcmp(line, second_last_line)) {
                EXPECT_TRUE(++count <= 1);
                fprintf(stderr, "%s(%zu):\n", entry->d_name, number);
            }
        }
        fclose(fp);
        free(line);
        unlink(command);
    }
    if (count > 1) {
        char *brk = strpbrk(second_last_line, "\r\n");
        if (!brk) {
            brk = second_last_line + strlen(second_last_line);
        }
        fprintf(stderr, "\"%.*s\" occured %u times\n",
            (int)(brk - second_last_line), second_last_line, count);
    }
    free(second_last_line);

    snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
    EXPECT_FALSE(system(command));
}

TEST(logcat, logrotate_clear) {
    static const char tmp_out_dir_form[] = "/data/local/tmp/logcat.logrotate.XXXXXX";
    char tmp_out_dir[sizeof(tmp_out_dir_form)];
    ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form)));

    static const char log_filename[] = "log.txt";
    static const unsigned num_val = 32;
    static const char logcat_cmd[] = "logcat -b all -d -f %s/%s -n %d -r 1";
    static const char clear_cmd[] = " -c";
    static const char cleanup_cmd[] = "rm -rf %s";
    char command[sizeof(tmp_out_dir) + sizeof(logcat_cmd) + sizeof(log_filename) + sizeof(clear_cmd) + 32];

    // Run command with all data
    {
        snprintf(command, sizeof(command) - sizeof(clear_cmd),
                 logcat_cmd, tmp_out_dir, log_filename, num_val);

        int ret;
        EXPECT_FALSE((ret = system(command)));
        if (ret) {
            snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
            EXPECT_FALSE(system(command));
            return;
        }
        std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir), closedir);
        EXPECT_NE(nullptr, dir);
        if (!dir) {
            snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
            EXPECT_FALSE(system(command));
            return;
        }
        struct dirent *entry;
        unsigned count = 0;
        while ((entry = readdir(dir.get()))) {
            if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) {
                continue;
            }
            ++count;
        }
        EXPECT_EQ(count, num_val + 1);
    }

    {
        // Now with -c option tacked onto the end
        strcat(command, clear_cmd);

        int ret;
        EXPECT_FALSE((ret = system(command)));
        if (ret) {
            snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
            EXPECT_FALSE(system(command));
            return;
        }
        std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir), closedir);
        EXPECT_NE(nullptr, dir);
        if (!dir) {
            snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
            EXPECT_FALSE(system(command));
            return;
        }
        struct dirent *entry;
        unsigned count = 0;
        while ((entry = readdir(dir.get()))) {
            if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) {
                continue;
            }
            fprintf(stderr, "Found %s/%s!!!\n", tmp_out_dir, entry->d_name);
            ++count;
        }
        EXPECT_EQ(count, 0U);
    }

    snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
    EXPECT_FALSE(system(command));
}

TEST(logcat, logrotate_nodir) {
    // expect logcat to error out on writing content and exit(1) for nodir
    EXPECT_EQ(W_EXITCODE(1, 0),
              system("logcat -b all -d"
                     " -f /das/nein/gerfingerpoken/logcat/log.txt"
                     " -n 256 -r 1024"));
}

static void caught_blocking_clear(int /*signum*/) {
    unsigned long long v = 0xDEADBEEFA55C0000ULL;

    v += getpid() & 0xFFFF;

    LOG_FAILURE_RETRY(__android_log_btwrite(0, EVENT_TYPE_LONG, &v, sizeof(v)));
}

TEST(logcat, blocking_clear) {
    FILE *fp;
    unsigned long long v = 0xDEADBEEFA55C0000ULL;

    pid_t pid = getpid();

    v += pid & 0xFFFF;

    // This test is racey; an event occurs between clear and dump.
    // We accept that we will get a false positive, but never a false negative.
    ASSERT_TRUE(NULL != (fp = popen(
      "( trap exit HUP QUIT INT PIPE KILL ; sleep 6; echo DONE )&"
      " logcat -b events -c 2>&1 ;"
      " logcat -b events -g 2>&1 ;"
      " logcat -v brief -b events 2>&1",
      "r")));

    char buffer[BIG_BUFFER];

    int count = 0;
    int minus_g = 0;

    int signals = 0;

    signal(SIGALRM, caught_blocking_clear);
    alarm(2);
    while (fgets(buffer, sizeof(buffer), fp)) {

        if (!strncmp(buffer, "clearLog: ", 10)) {
            fprintf(stderr, "WARNING: Test lacks permission to run :-(\n");
            count = signals = 1;
            break;
        }

        if (!strncmp(buffer, "DONE", 4)) {
            break;
        }

        int size, consumed, max, payload;
        char size_mult[3], consumed_mult[3];
        size = consumed = max = payload = 0;
        if (6 == sscanf(buffer, "events: ring buffer is %d%2s (%d%2s consumed),"
                                " max entry is %db, max payload is %db",
                                &size, size_mult, &consumed, consumed_mult,
                                &max, &payload)) {
            long full_size = size, full_consumed = consumed;

            switch(size_mult[0]) {
            case 'G':
                full_size *= 1024;
                /* FALLTHRU */
            case 'M':
                full_size *= 1024;
                /* FALLTHRU */
            case 'K':
                full_size *= 1024;
                /* FALLTHRU */
            case 'b':
                break;
            }
            switch(consumed_mult[0]) {
            case 'G':
                full_consumed *= 1024;
                /* FALLTHRU */
            case 'M':
                full_consumed *= 1024;
                /* FALLTHRU */
            case 'K':
                full_consumed *= 1024;
                /* FALLTHRU */
            case 'b':
                break;
            }
            EXPECT_GT(full_size, full_consumed);
            EXPECT_GT(full_size, max);
            EXPECT_GT(max, payload);
            EXPECT_GT(max, full_consumed);

            ++minus_g;
            continue;
        }

        ++count;

        int p;
        unsigned long long l;

        if ((2 != sscanf(buffer, "I/[0] ( %u): %lld", &p, &l))
         || (p != pid)) {
            continue;
        }

        if (l == v) {
            if (count > 1) {
                fprintf(stderr, "WARNING: Possible false positive\n");
            }
            ++signals;
            break;
        }
    }
    alarm(0);
    signal(SIGALRM, SIG_DFL);

    // Generate SIGPIPE
    fclose(fp);
    caught_blocking_clear(0);

    pclose(fp);

    EXPECT_LE(1, count);
    EXPECT_EQ(1, minus_g);

    EXPECT_EQ(1, signals);
}

static bool get_white_black(char **list) {
    FILE *fp;

    fp = popen("logcat -p 2>/dev/null", "r");
    if (fp == NULL) {
        fprintf(stderr, "ERROR: logcat -p 2>/dev/null\n");
        return false;
    }

    char buffer[BIG_BUFFER];

    while (fgets(buffer, sizeof(buffer), fp)) {
        char *hold = *list;
        char *buf = buffer;
	while (isspace(*buf)) {
            ++buf;
        }
        char *end = buf + strlen(buf);
        while (isspace(*--end) && (end >= buf)) {
            *end = '\0';
        }
        if (end < buf) {
            continue;
        }
        if (hold) {
            asprintf(list, "%s %s", hold, buf);
            free(hold);
        } else {
            asprintf(list, "%s", buf);
        }
    }
    pclose(fp);
    return *list != NULL;
}

static bool set_white_black(const char *list) {
    FILE *fp;

    char buffer[BIG_BUFFER];

    snprintf(buffer, sizeof(buffer), "logcat -P '%s' 2>&1", list ? list : "");
    fp = popen(buffer, "r");
    if (fp == NULL) {
        fprintf(stderr, "ERROR: %s\n", buffer);
        return false;
    }

    while (fgets(buffer, sizeof(buffer), fp)) {
        char *buf = buffer;
	while (isspace(*buf)) {
            ++buf;
        }
        char *end = buf + strlen(buf);
        while ((end > buf) && isspace(*--end)) {
            *end = '\0';
        }
        if (end <= buf) {
            continue;
        }
        fprintf(stderr, "%s\n", buf);
        pclose(fp);
        return false;
    }
    return pclose(fp) == 0;
}

TEST(logcat, white_black_adjust) {
    char *list = NULL;
    char *adjust = NULL;

    get_white_black(&list);

    static const char adjustment[] = "~! 300/20 300/25 2000 ~1000/5 ~1000/30";
    ASSERT_EQ(true, set_white_black(adjustment));
    ASSERT_EQ(true, get_white_black(&adjust));
    EXPECT_STREQ(adjustment, adjust);
    free(adjust);
    adjust = NULL;

    static const char adjustment2[] = "300/20 300/21 2000 ~1000";
    ASSERT_EQ(true, set_white_black(adjustment2));
    ASSERT_EQ(true, get_white_black(&adjust));
    EXPECT_STREQ(adjustment2, adjust);
    free(adjust);
    adjust = NULL;

    ASSERT_EQ(true, set_white_black(list));
    get_white_black(&adjust);
    EXPECT_STREQ(list ? list : "", adjust ? adjust : "");
    free(adjust);
    adjust = NULL;

    free(list);
    list = NULL;
}

TEST(logcat, regex) {
    FILE *fp;
    int count = 0;

    char buffer[BIG_BUFFER];

    snprintf(buffer, sizeof(buffer), "logcat --pid %d -d -e logcat_test_a+b", getpid());

    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test_ab"));
    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test_b"));
    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test_aaaab"));
    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test_aaaa"));

    // Let the logs settle
    sleep(1);

    ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));

    while (fgets(buffer, sizeof(buffer), fp)) {
        if (!strncmp(begin, buffer, sizeof(begin) - 1)) {
            continue;
        }

        EXPECT_TRUE(strstr(buffer, "logcat_test_") != NULL);

        count++;
    }

    pclose(fp);

    ASSERT_EQ(2, count);
}

TEST(logcat, maxcount) {
    FILE *fp;
    int count = 0;

    char buffer[BIG_BUFFER];

    snprintf(buffer, sizeof(buffer), "logcat --pid %d -d --max-count 3", getpid());

    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test"));
    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test"));
    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test"));
    LOG_FAILURE_RETRY(__android_log_print(ANDROID_LOG_WARN, "logcat_test", "logcat_test"));

    // Let the logs settle
    sleep(1);

    ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));

    while (fgets(buffer, sizeof(buffer), fp)) {
        if (!strncmp(begin, buffer, sizeof(begin) - 1)) {
            continue;
        }

        count++;
    }

    pclose(fp);

    ASSERT_EQ(3, count);
}
