Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 65e1e628 authored by Mark Salyzyn's avatar Mark Salyzyn
Browse files

liblog: retry -ENOTCONN | -ECONNREFUSED | -ENOENT indefinitely

Deal with recovering after transitory failures surrounding logd
crash or recovery.  Improve the chances that the logging functions
can work in a signal handler, not officially supported, but making
sure logging is not blamed for system lockups when misused.

Reorder gTests so that setuid(AID_SYSTEM) is performed after
liblog.enoent test, and that this occurs after other tests that
like to see buffers with content in them as we stop logd.

Test: gTest liblog-unit-tests --gtest_filter=liblog.enoent
Bug: 33755074
Change-Id: I66f88599534614b7b61da6b2ae5fe099ebaced3a
parent cdd7ec12
Loading
Loading
Loading
Loading
+70 −35
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ static int logdWrite(log_id_t logId, struct timespec *ts,

LIBLOG_HIDDEN struct android_log_transport_write logdLoggerWrite = {
    .node = { &logdLoggerWrite.node, &logdLoggerWrite.node },
    .context.sock = -1,
    .context.sock = -EBADF,
    .name = "logd",
    .available = logdAvailable,
    .open = logdOpen,
@@ -65,8 +65,10 @@ static int logdOpen()

    i = atomic_load(&logdLoggerWrite.context.sock);
    if (i < 0) {
        i = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
        if (i < 0) {
        int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM |
                                                      SOCK_CLOEXEC |
                                                      SOCK_NONBLOCK, 0));
        if (sock < 0) {
            ret = -errno;
        } else {
            struct sockaddr_un un;
@@ -74,13 +76,22 @@ static int logdOpen()
            un.sun_family = AF_UNIX;
            strcpy(un.sun_path, "/dev/socket/logdw");

            if (TEMP_FAILURE_RETRY(connect(i, (struct sockaddr *)&un,
            if (TEMP_FAILURE_RETRY(connect(sock, (struct sockaddr *)&un,
                                           sizeof(struct sockaddr_un))) < 0) {
                ret = -errno;
                close(i);
                switch (ret) {
                case -ENOTCONN:
                case -ECONNREFUSED:
                case -ENOENT:
                    i = atomic_exchange(&logdLoggerWrite.context.sock, ret);
                    /* FALLTHRU */
                default:
                    break;
                }
                close(sock);
            } else {
                ret = atomic_exchange(&logdLoggerWrite.context.sock, i);
                if ((ret >= 0) && (ret != i)) {
                ret = atomic_exchange(&logdLoggerWrite.context.sock, sock);
                if ((ret >= 0) && (ret != sock)) {
                    close(ret);
                }
                ret = 0;
@@ -91,14 +102,19 @@ static int logdOpen()
    return ret;
}

static void logdClose()
static void __logdClose(int negative_errno)
{
    int sock = atomic_exchange(&logdLoggerWrite.context.sock, -1);
    int sock = atomic_exchange(&logdLoggerWrite.context.sock, negative_errno);
    if (sock >= 0) {
        close(sock);
    }
}

static void logdClose()
{
    __logdClose(-EBADF);
}

static int logdAvailable(log_id_t logId)
{
    if (logId > LOG_ID_SECURITY) {
@@ -117,6 +133,7 @@ static int logdWrite(log_id_t logId, struct timespec *ts,
                     struct iovec *vec, size_t nr)
{
    ssize_t ret;
    int sock;
    static const unsigned headerLength = 1;
    struct iovec newVec[nr + headerLength];
    android_log_header_t header;
@@ -124,7 +141,13 @@ static int logdWrite(log_id_t logId, struct timespec *ts,
    static atomic_int_fast32_t dropped;
    static atomic_int_fast32_t droppedSecurity;

    if (atomic_load(&logdLoggerWrite.context.sock) < 0) {
    sock = atomic_load(&logdLoggerWrite.context.sock);
    if (sock < 0) switch (sock) {
    case -ENOTCONN:
    case -ECONNREFUSED:
    case -ENOENT:
        break;
    default:
        return -EBADF;
    }

@@ -163,7 +186,7 @@ static int logdWrite(log_id_t logId, struct timespec *ts,
    newVec[0].iov_base = (unsigned char *)&header;
    newVec[0].iov_len  = sizeof(header);

    if (atomic_load(&logdLoggerWrite.context.sock) > 0) {
    if (sock >= 0) {
        int32_t snapshot = atomic_exchange_explicit(&droppedSecurity, 0,
                                                    memory_order_relaxed);
        if (snapshot) {
@@ -177,8 +200,7 @@ static int logdWrite(log_id_t logId, struct timespec *ts,
            newVec[headerLength].iov_base = &buffer;
            newVec[headerLength].iov_len  = sizeof(buffer);

            ret = TEMP_FAILURE_RETRY(writev(
                    atomic_load(&logdLoggerWrite.context.sock), newVec, 2));
            ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
            if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
                atomic_fetch_add_explicit(&droppedSecurity, snapshot,
                                          memory_order_relaxed);
@@ -186,7 +208,8 @@ static int logdWrite(log_id_t logId, struct timespec *ts,
        }
        snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
        if (snapshot && __android_log_is_loggable_len(ANDROID_LOG_INFO,
                                                      "liblog", strlen("liblog"),
                                                      "liblog",
                                                      strlen("liblog"),
                                                      ANDROID_LOG_VERBOSE)) {
            android_log_event_int_t buffer;

@@ -198,8 +221,7 @@ static int logdWrite(log_id_t logId, struct timespec *ts,
            newVec[headerLength].iov_base = &buffer;
            newVec[headerLength].iov_len  = sizeof(buffer);

            ret = TEMP_FAILURE_RETRY(writev(
                      atomic_load(&logdLoggerWrite.context.sock), newVec, 2));
            ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
            if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
                atomic_fetch_add_explicit(&dropped, snapshot,
                                          memory_order_relaxed);
@@ -225,16 +247,27 @@ static int logdWrite(log_id_t logId, struct timespec *ts,
    /*
     * The write below could be lost, but will never block.
     *
     * ENOTCONN occurs if logd dies.
     * ENOTCONN occurs if logd has died.
     * ENOENT occurs if logd is not running and socket is missing.
     * ECONNREFUSED occurs if we can not reconnect to logd.
     * EAGAIN occurs if logd is overloaded.
     */
    ret = TEMP_FAILURE_RETRY(writev(
            atomic_load(&logdLoggerWrite.context.sock), newVec, i));
    if (sock < 0) {
        ret = sock;
    } else {
        ret = TEMP_FAILURE_RETRY(writev(sock, newVec, i));
        if (ret < 0) {
            ret = -errno;
        if (ret == -ENOTCONN) {
            __android_log_lock();
            logdClose();
        }
    }
    switch(ret) {
    case -ENOTCONN:
    case -ECONNREFUSED:
    case -ENOENT:
        if (__android_log_trylock()) {
            return ret; /* in a signal handler? try again when less stressed */
        }
        __logdClose(ret);
        ret = logdOpen();
        __android_log_unlock();

@@ -247,7 +280,9 @@ static int logdWrite(log_id_t logId, struct timespec *ts,
        if (ret < 0) {
            ret = -errno;
        }
        }
        /* FALLTHRU */
    default:
        break;
    }

    if (ret > (ssize_t)sizeof(header)) {
+291 −191
Original line number Diff line number Diff line
@@ -504,190 +504,6 @@ TEST(liblog, __android_log_buf_write_and_print__newline_space_prefix) {
    buf_write_test("\n Hello World \n");
}

TEST(liblog, __security) {
#ifdef __ANDROID__
    static const char persist_key[] = "persist.logd.security";
    static const char readonly_key[] = "ro.device_owner";
    static const char nothing_val[] = "_NOTHING_TO_SEE_HERE_";
    char persist[PROP_VALUE_MAX];
    char readonly[PROP_VALUE_MAX];

    property_get(persist_key, persist, "");
    property_get(readonly_key, readonly, nothing_val);

    if (!strcmp(readonly, nothing_val)) {
        EXPECT_FALSE(__android_log_security());
        fprintf(stderr, "Warning, setting ro.device_owner to a domain\n");
        property_set(readonly_key, "com.google.android.SecOps.DeviceOwner");
    } else if (!strcasecmp(readonly, "false") || !readonly[0]) {
        EXPECT_FALSE(__android_log_security());
        return;
    }

    if (!strcasecmp(persist, "true")) {
        EXPECT_TRUE(__android_log_security());
    } else {
        EXPECT_FALSE(__android_log_security());
    }
    property_set(persist_key, "TRUE");
    EXPECT_TRUE(__android_log_security());
    property_set(persist_key, "FALSE");
    EXPECT_FALSE(__android_log_security());
    property_set(persist_key, "true");
    EXPECT_TRUE(__android_log_security());
    property_set(persist_key, "false");
    EXPECT_FALSE(__android_log_security());
    property_set(persist_key, "");
    EXPECT_FALSE(__android_log_security());
    property_set(persist_key, persist);
#else
    GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
}

TEST(liblog, __security_buffer) {
#ifdef __ANDROID__
    struct logger_list *logger_list;
    android_event_long_t buffer;

    static const char persist_key[] = "persist.logd.security";
    char persist[PROP_VALUE_MAX];
    bool set_persist = false;
    bool allow_security = false;

    if (__android_log_security()) {
        allow_security = true;
    } else {
        property_get(persist_key, persist, "");
        if (strcasecmp(persist, "true")) {
            property_set(persist_key, "TRUE");
            if (__android_log_security()) {
                allow_security = true;
                set_persist = true;
            } else {
                property_set(persist_key, persist);
            }
        }
    }

    if (!allow_security) {
        fprintf(stderr, "WARNING: "
                "security buffer disabled, bypassing end-to-end test\n");

        log_time ts(CLOCK_MONOTONIC);

        buffer.type = EVENT_TYPE_LONG;
        buffer.data = *(static_cast<uint64_t *>((void *)&ts));

        // expect failure!
        ASSERT_GE(0, __android_log_security_bwrite(0, &buffer, sizeof(buffer)));

        return;
    }

    /* Matches clientHasLogCredentials() in logd */
    uid_t uid = getuid();
    gid_t gid = getgid();
    bool clientHasLogCredentials = true;
    if ((uid != AID_SYSTEM) && (uid != AID_ROOT) && (uid != AID_LOG)
     && (gid != AID_SYSTEM) && (gid != AID_ROOT) && (gid != AID_LOG)) {
        uid_t euid = geteuid();
        if ((euid != AID_SYSTEM) && (euid != AID_ROOT) && (euid != AID_LOG)) {
            gid_t egid = getegid();
            if ((egid != AID_SYSTEM) && (egid != AID_ROOT) && (egid != AID_LOG)) {
                int num_groups = getgroups(0, NULL);
                if (num_groups > 0) {
                    gid_t groups[num_groups];
                    num_groups = getgroups(num_groups, groups);
                    while (num_groups > 0) {
                        if (groups[num_groups - 1] == AID_LOG) {
                            break;
                        }
                        --num_groups;
                    }
                }
                if (num_groups <= 0) {
                    clientHasLogCredentials = false;
                }
            }
        }
    }
    if (!clientHasLogCredentials) {
        fprintf(stderr, "WARNING: "
                "not in system context, bypassing end-to-end test\n");

        log_time ts(CLOCK_MONOTONIC);

        buffer.type = EVENT_TYPE_LONG;
        buffer.data = *(static_cast<uint64_t *>((void *)&ts));

        // expect failure!
        ASSERT_GE(0, __android_log_security_bwrite(0, &buffer, sizeof(buffer)));

        return;
    }

    setuid(AID_SYSTEM); // only one that can read security buffer

    pid_t pid = getpid();

    ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
        LOG_ID_SECURITY, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK,
        1000, pid)));

    log_time ts(CLOCK_MONOTONIC);

    buffer.type = EVENT_TYPE_LONG;
    buffer.data = *(static_cast<uint64_t *>((void *)&ts));

    ASSERT_LT(0, __android_log_security_bwrite(0, &buffer, sizeof(buffer)));
    usleep(1000000);

    int count = 0;

    for (;;) {
        log_msg log_msg;
        if (android_logger_list_read(logger_list, &log_msg) <= 0) {
            break;
        }

        ASSERT_EQ(log_msg.entry.pid, pid);

        if ((log_msg.entry.len != sizeof(android_log_event_long_t))
         || (log_msg.id() != LOG_ID_SECURITY)) {
            continue;
        }

        android_log_event_long_t* eventData;
        eventData = reinterpret_cast<android_log_event_long_t*>(log_msg.msg());

        if (!eventData || (eventData->payload.type != EVENT_TYPE_LONG)) {
            continue;
        }

        log_time tx(reinterpret_cast<char*>(&eventData->payload.data));
        if (ts == tx) {
            ++count;
        }
    }

    if (set_persist) {
        property_set(persist_key, persist);
    }

    android_logger_list_close(logger_list);

    bool clientHasSecurityCredentials = (uid == AID_SYSTEM) || (gid == AID_SYSTEM);
    if (!clientHasSecurityCredentials) {
        fprintf(stderr, "WARNING: "
                "not system, content submitted but can not check end-to-end\n");
    }
    EXPECT_EQ(clientHasSecurityCredentials ? 1 : 0, count);
#else
    GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
}

#ifdef __ANDROID__
static unsigned signaled;
static log_time signal_time;
@@ -1375,6 +1191,9 @@ TEST(liblog, dual_reader) {

TEST(liblog, android_logger_get_) {
#ifdef __ANDROID__
    // This test assumes the log buffers are filled with noise from
    // normal operations. It will fail if done immediately after a
    // logcat -c.
    struct logger_list * logger_list = android_logger_list_alloc(ANDROID_LOG_WRONLY, 0, 0);

    for(int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
@@ -1785,6 +1604,294 @@ TEST(liblog, is_loggable) {
#endif
}

#ifdef __ANDROID__
// helper to liblog.enoent to count end-to-end matching logging messages.
static int count_matching_ts(log_time ts) {
    usleep(1000000);

    pid_t pid = getpid();

    struct logger_list* logger_list = android_logger_list_open(
        LOG_ID_EVENTS, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 1000, pid);

    int count = 0;
    if (logger_list == NULL) return count;

    for (;;) {
        log_msg log_msg;
        if (android_logger_list_read(logger_list, &log_msg) <= 0) break;

        if (log_msg.entry.len != sizeof(android_log_event_long_t)) continue;
        if (log_msg.id() != LOG_ID_EVENTS) continue;

        android_log_event_long_t* eventData;
        eventData = reinterpret_cast<android_log_event_long_t*>(log_msg.msg());
        if (!eventData) continue;
        if (eventData->payload.type != EVENT_TYPE_LONG) continue;

        log_time tx(reinterpret_cast<char*>(&eventData->payload.data));
        if (ts != tx) continue;

        // found event message with matching timestamp signature in payload
        ++count;
    }
    android_logger_list_close(logger_list);

    return count;
}

// meant to be handed to ASSERT_TRUE / EXPECT_TRUE only to expand the message
static testing::AssertionResult IsOk(bool ok, std::string &message) {
    return ok ?
        testing::AssertionSuccess() :
        (testing::AssertionFailure() << message);
}
#endif

TEST(liblog, enoent) {
#ifdef __ANDROID__
    log_time ts(CLOCK_MONOTONIC);
    EXPECT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
    EXPECT_EQ(1, count_matching_ts(ts));

    // This call will fail if we are setuid(AID_SYSTEM), beware of any
    // test prior to this one playing with setuid and causing interference.
    // We need to run before these tests so that they do not interfere with
    // this test.
    //
    // Stopping the logger can affect some other test's expectations as they
    // count on the log buffers filled with existing content, and this
    // effectively does a logcat -c emptying it.  So we want this test to be
    // as near as possible to the bottom of the file.  For example
    // liblog.android_logger_get_ is one of those tests that has no recourse
    // and that would be adversely affected by emptying the log if it was run
    // right after this test.
    system("stop logd");
    usleep(1000000);

    // A clean stop like we are testing returns -ENOENT, but in the _real_
    // world we could get -ENOTCONN or -ECONNREFUSED depending on timing.
    // Alas we can not test these other return values; accept that they
    // are treated equally within the open-retry logic in liblog.
    ts = log_time(CLOCK_MONOTONIC);
    int ret = __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts));
    std::string content = android::base::StringPrintf(
        "__android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)) = %d %s\n",
        ret, strerror(-ret));
    EXPECT_TRUE(IsOk((ret == -ENOENT) ||
                     (ret == -ENOTCONN) ||
                     (ret == -ECONNREFUSED), content));
    ret = __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts));
    content = android::base::StringPrintf(
        "__android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)) = %d %s\n",
        ret, strerror(-ret));
    EXPECT_TRUE(IsOk((ret == -ENOENT) ||
                     (ret == -ENOTCONN) ||
                     (ret == -ECONNREFUSED), content));
    EXPECT_EQ(0, count_matching_ts(ts));

    system("start logd");
    usleep(1000000);

    EXPECT_EQ(0, count_matching_ts(ts));

    ts = log_time(CLOCK_MONOTONIC);
    EXPECT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
    EXPECT_EQ(1, count_matching_ts(ts));

#else
    GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
}

// Below this point we run risks of setuid(AID_SYSTEM) which may affect others.

TEST(liblog, __security) {
#ifdef __ANDROID__
    static const char persist_key[] = "persist.logd.security";
    static const char readonly_key[] = "ro.device_owner";
    // A silly default value that can never be in readonly_key so
    // that it can be determined the property is not set.
    static const char nothing_val[] = "_NOTHING_TO_SEE_HERE_";
    char persist[PROP_VALUE_MAX];
    char readonly[PROP_VALUE_MAX];

    property_get(persist_key, persist, "");
    property_get(readonly_key, readonly, nothing_val);

    if (!strcmp(readonly, nothing_val)) {
        EXPECT_FALSE(__android_log_security());
        fprintf(stderr, "Warning, setting ro.device_owner to a domain\n");
        property_set(readonly_key, "com.google.android.SecOps.DeviceOwner");
    } else if (!strcasecmp(readonly, "false") || !readonly[0]) {
        EXPECT_FALSE(__android_log_security());
        return;
    }

    if (!strcasecmp(persist, "true")) {
        EXPECT_TRUE(__android_log_security());
    } else {
        EXPECT_FALSE(__android_log_security());
    }
    property_set(persist_key, "TRUE");
    EXPECT_TRUE(__android_log_security());
    property_set(persist_key, "FALSE");
    EXPECT_FALSE(__android_log_security());
    property_set(persist_key, "true");
    EXPECT_TRUE(__android_log_security());
    property_set(persist_key, "false");
    EXPECT_FALSE(__android_log_security());
    property_set(persist_key, "");
    EXPECT_FALSE(__android_log_security());
    property_set(persist_key, persist);
#else
    GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
}

TEST(liblog, __security_buffer) {
#ifdef __ANDROID__
    struct logger_list *logger_list;
    android_event_long_t buffer;

    static const char persist_key[] = "persist.logd.security";
    char persist[PROP_VALUE_MAX];
    bool set_persist = false;
    bool allow_security = false;

    if (__android_log_security()) {
        allow_security = true;
    } else {
        property_get(persist_key, persist, "");
        if (strcasecmp(persist, "true")) {
            property_set(persist_key, "TRUE");
            if (__android_log_security()) {
                allow_security = true;
                set_persist = true;
            } else {
                property_set(persist_key, persist);
            }
        }
    }

    if (!allow_security) {
        fprintf(stderr, "WARNING: "
                "security buffer disabled, bypassing end-to-end test\n");

        log_time ts(CLOCK_MONOTONIC);

        buffer.type = EVENT_TYPE_LONG;
        buffer.data = *(static_cast<uint64_t *>((void *)&ts));

        // expect failure!
        ASSERT_GE(0, __android_log_security_bwrite(0, &buffer, sizeof(buffer)));

        return;
    }

    /* Matches clientHasLogCredentials() in logd */
    uid_t uid = getuid();
    gid_t gid = getgid();
    bool clientHasLogCredentials = true;
    if ((uid != AID_SYSTEM) && (uid != AID_ROOT) && (uid != AID_LOG)
     && (gid != AID_SYSTEM) && (gid != AID_ROOT) && (gid != AID_LOG)) {
        uid_t euid = geteuid();
        if ((euid != AID_SYSTEM) && (euid != AID_ROOT) && (euid != AID_LOG)) {
            gid_t egid = getegid();
            if ((egid != AID_SYSTEM) && (egid != AID_ROOT) && (egid != AID_LOG)) {
                int num_groups = getgroups(0, NULL);
                if (num_groups > 0) {
                    gid_t groups[num_groups];
                    num_groups = getgroups(num_groups, groups);
                    while (num_groups > 0) {
                        if (groups[num_groups - 1] == AID_LOG) {
                            break;
                        }
                        --num_groups;
                    }
                }
                if (num_groups <= 0) {
                    clientHasLogCredentials = false;
                }
            }
        }
    }
    if (!clientHasLogCredentials) {
        fprintf(stderr, "WARNING: "
                "not in system context, bypassing end-to-end test\n");

        log_time ts(CLOCK_MONOTONIC);

        buffer.type = EVENT_TYPE_LONG;
        buffer.data = *(static_cast<uint64_t *>((void *)&ts));

        // expect failure!
        ASSERT_GE(0, __android_log_security_bwrite(0, &buffer, sizeof(buffer)));

        return;
    }

    setuid(AID_SYSTEM); // only one that can read security buffer

    pid_t pid = getpid();

    ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
        LOG_ID_SECURITY, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK,
        1000, pid)));

    log_time ts(CLOCK_MONOTONIC);

    buffer.type = EVENT_TYPE_LONG;
    buffer.data = *(static_cast<uint64_t *>((void *)&ts));

    ASSERT_LT(0, __android_log_security_bwrite(0, &buffer, sizeof(buffer)));
    usleep(1000000);

    int count = 0;

    for (;;) {
        log_msg log_msg;
        if (android_logger_list_read(logger_list, &log_msg) <= 0) {
            break;
        }

        ASSERT_EQ(log_msg.entry.pid, pid);

        if ((log_msg.entry.len != sizeof(android_log_event_long_t))
         || (log_msg.id() != LOG_ID_SECURITY)) {
            continue;
        }

        android_log_event_long_t* eventData;
        eventData = reinterpret_cast<android_log_event_long_t*>(log_msg.msg());

        if (!eventData || (eventData->payload.type != EVENT_TYPE_LONG)) {
            continue;
        }

        log_time tx(reinterpret_cast<char*>(&eventData->payload.data));
        if (ts == tx) {
            ++count;
        }
    }

    if (set_persist) {
        property_set(persist_key, persist);
    }

    android_logger_list_close(logger_list);

    bool clientHasSecurityCredentials = (uid == AID_SYSTEM) || (gid == AID_SYSTEM);
    if (!clientHasSecurityCredentials) {
        fprintf(stderr, "WARNING: "
                "not system, content submitted but can not check end-to-end\n");
    }
    EXPECT_EQ(clientHasSecurityCredentials ? 1 : 0, count);
#else
    GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
}

#ifdef __ANDROID__
static void android_errorWriteWithInfoLog_helper(int TAG, const char* SUBTAG,
                                                 int UID, const char* payload,
@@ -2803,13 +2910,6 @@ TEST(liblog, __android_log_pmsg_file_read) {
}

#ifdef __ANDROID__
// meant to be handed to ASSERT_TRUE / EXPECT_TRUE only to expand the message
static testing::AssertionResult IsOk(bool ok, std::string &message) {
    return ok ?
        testing::AssertionSuccess() :
        (testing::AssertionFailure() << message);
}

// must be: '<needle:> 0 kB'
static bool isZero(const std::string &content, std::string::size_type pos,
                   const char* needle) {