liblog: add __android_log_pmsg_file_write

(cherry pick from commit d4b061bde280fce9b5426b5738a02d42ec263c48)

- This is considered an Android Private function, not exported
  for general use.
- goal is to record a file's content into a series of log
  messages into pmsg, to be retrieved after a reboot for
  transfer to a persistent location.
- filename reference is converted to a tag-unique
  "<dirbase>:<filebase>".
- buffer and length representing the filename contents are
  recorded, along with a sequence number placed into the nsec
  time field to ANDROID_LOG_PMSG_FILE_MAX_SEQUENCE.
- Add a gTest for this function.

Bug: 27176738
Change-Id: If93df3ae8bfc1bb75516d4a1fd8dae0301af644b
diff --git a/include/private/android_logger.h b/include/private/android_logger.h
index 04238a6..a637ca5 100644
--- a/include/private/android_logger.h
+++ b/include/private/android_logger.h
@@ -19,7 +19,10 @@
 #ifndef _SYSTEM_CORE_INCLUDE_PRIVATE_ANDROID_LOGGER_H_
 #define _SYSTEM_CORE_INCLUDE_PRIVATE_ANDROID_LOGGER_H_
 
+/* Android private interfaces */
+
 #include <stdint.h>
+#include <sys/types.h>
 
 #include <log/log.h>
 #include <log/log_read.h>
@@ -95,4 +98,21 @@
     char data[];
 } android_log_event_string_t;
 
+#if defined(__cplusplus)
+extern "C" {
 #endif
+
+#define ANDROID_LOG_PMSG_FILE_MAX_SEQUENCE 256 /* 1MB file */
+#define ANDROID_LOG_PMSG_FILE_SEQUENCE     1000
+
+ssize_t __android_log_pmsg_file_write(
+        log_id_t logId,
+        char prio,
+        const char *filename,
+        const char *buf, size_t len);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* _SYSTEM_CORE_INCLUDE_PRIVATE_ANDROID_LOGGER_H_ */
diff --git a/liblog/pmsg_writer.c b/liblog/pmsg_writer.c
index 7a89e5d..7034ceb 100644
--- a/liblog/pmsg_writer.c
+++ b/liblog/pmsg_writer.c
@@ -157,3 +157,114 @@
 
     return ret;
 }
+
+/*
+ * Virtual pmsg filesystem
+ *
+ * Payload will comprise the string "<basedir>:<basefile>\0<content>" to a
+ * maximum of LOGGER_ENTRY_MAX_PAYLOAD, but scaled to the last newline in the
+ * file.
+ *
+ * Will hijack the header.realtime.tv_nsec field for a sequence number in usec.
+ */
+
+static inline const char *strnrchr(const char *buf, size_t len, char c) {
+    const char *cp = buf + len;
+    while ((--cp > buf) && (*cp != c));
+    if (cp <= buf) {
+        return buf + len;
+    }
+    return cp;
+}
+
+/* Write a buffer as filename references (tag = <basedir>:<basename>) */
+LIBLOG_ABI_PRIVATE ssize_t __android_log_pmsg_file_write(
+        log_id_t logId,
+        char prio,
+        const char *filename,
+        const char *buf, size_t len) {
+    int fd;
+    size_t length, packet_len;
+    const char *tag;
+    char *cp, *slash;
+    struct timespec ts;
+    struct iovec vec[3];
+
+    /* Make sure the logId value is not a bad idea */
+    if ((logId == LOG_ID_KERNEL) ||       /* Verbotten */
+            (logId == LOG_ID_EVENTS) ||   /* Do not support binary content */
+            (logId == LOG_ID_SECURITY) || /* Bad idea to allow */
+            ((unsigned)logId >= 32)) {    /* fit within logMask on arch32 */
+        return -EINVAL;
+    }
+
+    clock_gettime(android_log_clockid(), &ts);
+
+    cp = strdup(filename);
+    if (!cp) {
+        return -ENOMEM;
+    }
+
+    fd = pmsgLoggerWrite.context.fd;
+    if (fd < 0) {
+        __android_log_lock();
+        fd = pmsgOpen();
+        __android_log_unlock();
+        if (fd < 0) {
+            return -EBADF;
+        }
+    }
+
+    tag = cp;
+    slash = strrchr(cp, '/');
+    if (slash) {
+        *slash = ':';
+        slash = strrchr(cp, '/');
+        if (slash) {
+            tag = slash + 1;
+        }
+    }
+
+    length = strlen(tag) + 1;
+    packet_len = LOGGER_ENTRY_MAX_PAYLOAD - sizeof(char) - length;
+
+    vec[0].iov_base = &prio;
+    vec[0].iov_len  = sizeof(char);
+    vec[1].iov_base = (unsigned char *)tag;
+    vec[1].iov_len  = length;
+
+    for (ts.tv_nsec = 0, length = len;
+            length;
+            ts.tv_nsec += ANDROID_LOG_PMSG_FILE_SEQUENCE) {
+        ssize_t ret;
+        size_t transfer;
+
+        if ((ts.tv_nsec / ANDROID_LOG_PMSG_FILE_SEQUENCE) >=
+                ANDROID_LOG_PMSG_FILE_MAX_SEQUENCE) {
+            len -= length;
+            break;
+        }
+
+        transfer = length;
+        if (transfer > packet_len) {
+            transfer = strnrchr(buf, packet_len - 1, '\n') - buf;
+            if ((transfer < length) && (buf[transfer] == '\n')) {
+                ++transfer;
+            }
+        }
+
+        vec[2].iov_base = (unsigned char *)buf;
+        vec[2].iov_len  = transfer;
+
+        ret = pmsgWrite(logId, &ts, vec, sizeof(vec) / sizeof(vec[0]));
+
+        if (ret <= 0) {
+            free(cp);
+            return ret;
+        }
+        length -= transfer;
+        buf += transfer;
+    }
+    free(cp);
+    return len;
+}
diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
index af3a3b4..97391d4 100644
--- a/liblog/tests/liblog_test.cpp
+++ b/liblog/tests/liblog_test.cpp
@@ -896,7 +896,10 @@
 trouble: the fashion of the world is to avoid\n\
 cost, and you encounter it\n\
 LEONATO\n\
-Never came trouble to my house in the likeness of your grace";
+Never came trouble to my house in the likeness of your grace,\n\
+for trouble being gone, comfort should remain, but\n\
+when you depart from me, sorrow abides and happiness\n\
+takes his leave.";
 
 TEST(liblog, max_payload) {
     pid_t pid = getpid();
@@ -2451,3 +2454,13 @@
     EXPECT_LE(0, android_log_destroy(&ctx));
     ASSERT_TRUE(NULL == ctx);
 }
+
+static const char __pmsg_file[] =
+        "/data/william-shakespeare/MuchAdoAboutNothing.txt";
+
+TEST(liblog, __android_log_pmsg_file_write) {
+    EXPECT_LT(0, __android_log_pmsg_file_write(
+            LOG_ID_CRASH, ANDROID_LOG_VERBOSE,
+            __pmsg_file, max_payload_buf, sizeof(max_payload_buf)));
+    fprintf(stderr, "Reboot, ensure file %s matches\n", __pmsg_file);
+}