From 864e8e80e4f6b325ddacc25b0bd5d621eff0fb3a Mon Sep 17 00:00:00 2001 From: Mark Salyzyn Date: Mon, 14 Mar 2016 14:15:50 -0700 Subject: [PATCH] liblog: add __android_log_pmsg_file_read - This is considered an Android Private function, not exported for general use. - goal is to retreive a file's content from a series of log messages from pmsg, to be retrieved after a reboot for transfer to a persistent location. - files are presented in reverse sorted order, first based on _any_ numerical content, then by alphanumeric order. - Add a gTest for this function, relies on gTest for liblog.__android_log_pmsg_file_write from prior to reboot. Bug: 27176738 Change-Id: If37ef423009bd28b598b233af3bccef3429bdc22 --- include/private/android_logger.h | 14 ++ liblog/pmsg_reader.c | 332 +++++++++++++++++++++++++++++++ liblog/tests/liblog_test.cpp | 45 ++++- 3 files changed, 390 insertions(+), 1 deletion(-) diff --git a/include/private/android_logger.h b/include/private/android_logger.h index a637ca572..c3ea1ed7b 100644 --- a/include/private/android_logger.h +++ b/include/private/android_logger.h @@ -111,6 +111,20 @@ ssize_t __android_log_pmsg_file_write( const char *filename, const char *buf, size_t len); +#define LOG_ID_ANY ((log_id_t)-1) +#define ANDROID_LOG_ANY ANDROID_LOG_UNKNOWN + +/* first 5 arguments match __android_log_msg_file_write, a cast is safe */ +typedef ssize_t (*__android_log_pmsg_file_read_fn)( + log_id_t logId, + char prio, + const char *filename, + const char *buf, size_t len, void *arg); + +ssize_t __android_log_pmsg_file_read( + log_id_t logId, char prio, const char *prefix, + __android_log_pmsg_file_read_fn fn, void *arg); + #if defined(__cplusplus) } #endif diff --git a/liblog/pmsg_reader.c b/liblog/pmsg_reader.c index de435f7ed..5695e8ae5 100644 --- a/liblog/pmsg_reader.c +++ b/liblog/pmsg_reader.c @@ -14,9 +14,11 @@ * limitations under the License. */ +#include #include #include #include +#include #include #include @@ -253,3 +255,333 @@ static void pmsgClose(struct android_log_logger_list *logger_list __unused, } transp->context.fd = 0; } + +LIBLOG_ABI_PRIVATE ssize_t __android_log_pmsg_file_read( + log_id_t logId, + char prio, + const char *prefix, + __android_log_pmsg_file_read_fn fn, void *arg) { + ssize_t ret; + struct android_log_logger_list logger_list; + struct android_log_transport_context transp; + struct content { + struct listnode node; + union { + struct logger_entry_v4 entry; + struct logger_entry_v4 entry_v4; + struct logger_entry_v3 entry_v3; + struct logger_entry_v2 entry_v2; + struct logger_entry entry_v1; + }; + } *content; + struct names { + struct listnode node; + struct listnode content; + log_id_t id; + char prio; + char name[]; + } *names; + struct listnode name_list; + struct listnode *node, *n; + size_t len, prefix_len; + + if (!fn) { + return -EINVAL; + } + + /* Add just enough clues in logger_list and transp to make API function */ + memset(&logger_list, 0, sizeof(logger_list)); + memset(&transp, 0, sizeof(transp)); + + logger_list.mode = ANDROID_LOG_PSTORE | + ANDROID_LOG_NONBLOCK | + ANDROID_LOG_RDONLY; + transp.logMask = (unsigned)-1; + if (logId != LOG_ID_ANY) { + transp.logMask = (1 << logId); + } + transp.logMask &= ~((1 << LOG_ID_KERNEL) | + (1 << LOG_ID_EVENTS) | + (1 << LOG_ID_SECURITY)); + if (!transp.logMask) { + return -EINVAL; + } + + /* Initialize name list */ + list_init(&name_list); + + ret = SSIZE_MAX; + + /* Validate incoming prefix, shift until it contains only 0 or 1 : or / */ + prefix_len = 0; + if (prefix) { + const char *prev = NULL, *last = NULL, *cp = prefix; + while ((cp = strpbrk(cp, "/:"))) { + prev = last; + last = cp; + cp = cp + 1; + } + if (prev) { + prefix = prev + 1; + } + prefix_len = strlen(prefix); + } + + /* Read the file content */ + while (pmsgRead(&logger_list, &transp, &transp.logMsg) > 0) { + char *cp; + size_t hdr_size = transp.logMsg.entry.hdr_size ? + transp.logMsg.entry.hdr_size : sizeof(transp.logMsg.entry_v1); + char *msg = (char *)&transp.logMsg + hdr_size; + char *split = NULL; + + /* Check for invalid sequence number */ + if ((transp.logMsg.entry.nsec % ANDROID_LOG_PMSG_FILE_SEQUENCE) || + ((transp.logMsg.entry.nsec / ANDROID_LOG_PMSG_FILE_SEQUENCE) >= + ANDROID_LOG_PMSG_FILE_MAX_SEQUENCE)) { + continue; + } + + /* Determine if it has : format for tag */ + len = transp.logMsg.entry.len - sizeof(prio); + for (cp = msg + sizeof(prio); + *cp && isprint(*cp) && !isspace(*cp) && --len; + ++cp) { + if (*cp == ':') { + if (split) { + break; + } + split = cp; + } + } + if (*cp || !split) { + continue; + } + + /* Filters */ + if (prefix_len && strncmp(msg + sizeof(prio), prefix, prefix_len)) { + size_t offset; + /* + * Allow : to be a synonym for / + * Things we do dealing with const char * and do not alloc + */ + split = strchr(prefix, ':'); + if (split) { + continue; + } + split = strchr(prefix, '/'); + if (!split) { + continue; + } + offset = split - prefix; + if ((msg[offset + sizeof(prio)] != ':') || + strncmp(msg + sizeof(prio), prefix, offset)) { + continue; + } + ++offset; + if ((prefix_len > offset) && + strncmp(&msg[offset + sizeof(prio)], split + 1, prefix_len - offset)) { + continue; + } + } + + if ((prio != ANDROID_LOG_ANY) && (*msg < prio)) { + continue; + } + + /* check if there is an existing entry */ + list_for_each(node, &name_list) { + names = node_to_item(node, struct names, node); + if (!strcmp(names->name, msg + sizeof(prio)) && + (names->id == transp.logMsg.entry.lid) && + (names->prio == *msg)) { + break; + } + } + + /* We do not have an existing entry, create and add one */ + if (node == &name_list) { + static const char numbers[] = "0123456789"; + unsigned long long nl; + + len = strlen(msg + sizeof(prio)) + 1; + names = calloc(1, sizeof(*names) + len); + if (!names) { + ret = -ENOMEM; + break; + } + strcpy(names->name, msg + sizeof(prio)); + names->id = transp.logMsg.entry.lid; + names->prio = *msg; + list_init(&names->content); + /* + * Insert in reverse numeric _then_ alpha sorted order as + * representative of log rotation: + * + * log.10 + * klog.10 + * . . . + * log.2 + * klog.2 + * log.1 + * klog.1 + * log + * klog + * + * thus when we present the content, we are provided the oldest + * first, which when 'refreshed' could spill off the end of the + * pmsg FIFO but retaining the newest data for last with best + * chances to survive. + */ + nl = 0; + cp = strpbrk(names->name, numbers); + if (cp) { + nl = strtoull(cp, NULL, 10); + } + list_for_each_reverse(node, &name_list) { + struct names *a_name = node_to_item(node, struct names, node); + const char *r = a_name->name; + int compare = 0; + + unsigned long long nr = 0; + cp = strpbrk(r, numbers); + if (cp) { + nr = strtoull(cp, NULL, 10); + } + if (nr != nl) { + compare = (nl > nr) ? 1 : -1; + } + if (compare == 0) { + compare = strcmp(names->name, r); + } + if (compare <= 0) { + break; + } + } + list_add_head(node, &names->node); + } + + /* Remove any file fragments that match our sequence number */ + list_for_each_safe(node, n, &names->content) { + content = node_to_item(node, struct content, node); + if (transp.logMsg.entry.nsec == content->entry.nsec) { + list_remove(&content->node); + free(content); + } + } + + /* Add content */ + content = calloc(1, sizeof(content->node) + + hdr_size + transp.logMsg.entry.len); + if (!content) { + ret = -ENOMEM; + break; + } + memcpy(&content->entry, &transp.logMsg.entry, + hdr_size + transp.logMsg.entry.len); + + /* Insert in sequence number sorted order, to ease reconstruction */ + list_for_each_reverse(node, &names->content) { + if ((node_to_item(node, struct content, node))->entry.nsec < + transp.logMsg.entry.nsec) { + break; + } + } + list_add_head(node, &content->node); + } + pmsgClose(&logger_list, &transp); + + /* Progress through all the collected files */ + list_for_each_safe(node, n, &name_list) { + struct listnode *content_node, *m; + char *buf; + size_t sequence, tag_len; + + names = node_to_item(node, struct names, node); + + /* Construct content into a linear buffer */ + buf = NULL; + len = 0; + sequence = 0; + tag_len = strlen(names->name) + sizeof(char); /* tag + nul */ + list_for_each_safe(content_node, m, &names->content) { + ssize_t add_len; + + content = node_to_item(content_node, struct content, node); + add_len = content->entry.len - tag_len - sizeof(prio); + if (add_len <= 0) { + list_remove(content_node); + free(content); + continue; + } + + if (!buf) { + buf = malloc(sizeof(char)); + if (!buf) { + ret = -ENOMEM; + list_remove(content_node); + free(content); + continue; + } + *buf = '\0'; + } + + /* Missing sequence numbers */ + while (sequence < content->entry.nsec) { + /* plus space for enforced nul */ + buf = realloc(buf, len + sizeof(char) + sizeof(char)); + if (!buf) { + break; + } + buf[len] = '\f'; /* Mark missing content with a form feed */ + buf[++len] = '\0'; + sequence += ANDROID_LOG_PMSG_FILE_SEQUENCE; + } + if (!buf) { + ret = -ENOMEM; + list_remove(content_node); + free(content); + continue; + } + /* plus space for enforced nul */ + buf = realloc(buf, len + add_len + sizeof(char)); + if (!buf) { + ret = -ENOMEM; + list_remove(content_node); + free(content); + continue; + } + memcpy(buf + len, + (char *)&content->entry + content->entry.hdr_size + + tag_len + sizeof(prio), + add_len); + len += add_len; + buf[len] = '\0'; /* enforce trailing hidden nul */ + sequence = content->entry.nsec + ANDROID_LOG_PMSG_FILE_SEQUENCE; + + list_remove(content_node); + free(content); + } + if (buf) { + if (len) { + /* Buffer contains enforced trailing nul just beyond length */ + ssize_t r; + *strchr(names->name, ':') = '/'; /* Convert back to filename */ + r = (*fn)(names->id, names->prio, names->name, buf, len, arg); + if ((ret >= 0) && (r > 0)) { + if (ret == SSIZE_MAX) { + ret = r; + } else { + ret += r; + } + } else if (r < ret) { + ret = r; + } + } + free(buf); + } + list_remove(node); + free(names); + } + return (ret == SSIZE_MAX) ? -ENOENT : ret; +} diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp index f3157913e..941b9b9d3 100644 --- a/liblog/tests/liblog_test.cpp +++ b/liblog/tests/liblog_test.cpp @@ -2531,5 +2531,48 @@ 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); + fprintf(stderr, "Reboot, ensure file %s matches\n" + "with liblog.__android_log_msg_file_read test\n", + __pmsg_file); +} + +ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename, + const char *buf, size_t len, void *arg) { + EXPECT_TRUE(NULL == arg); + EXPECT_EQ(LOG_ID_CRASH, logId); + EXPECT_EQ(ANDROID_LOG_VERBOSE, prio); + EXPECT_FALSE(NULL == strstr(__pmsg_file, filename)); + EXPECT_EQ(len, sizeof(max_payload_buf)); + EXPECT_EQ(0, strcmp(max_payload_buf, buf)); + + ++signaled; + if ((len != sizeof(max_payload_buf)) || + strcmp(max_payload_buf, buf)) { + fprintf(stderr, "comparison fails on content \"%s\"\n", buf); + } + return !arg || + (LOG_ID_CRASH != logId) || + (ANDROID_LOG_VERBOSE != prio) || + !strstr(__pmsg_file, filename) || + (len != sizeof(max_payload_buf)) || + !!strcmp(max_payload_buf, buf) ? -ENOEXEC : 1; +} + +TEST(liblog, __android_log_pmsg_file_read) { + signaled = 0; + + ssize_t ret = __android_log_pmsg_file_read( + LOG_ID_CRASH, ANDROID_LOG_VERBOSE, + __pmsg_file, __pmsg_fn, NULL); + + if (ret == -ENOENT) { + fprintf(stderr, + "No pre-boot results of liblog.__android_log_mesg_file_write to " + "compare with,\n" + "false positive test result.\n"); + return; + } + + EXPECT_LT(0, ret); + EXPECT_EQ(1U, signaled); }