#include #include #include #include #include "logcat_entry.h" #include "pcre2_wrapper.h" static const Pcre2Regex LogcatEntryRegex("^\\s*(\\d+)(?:\\.\\d+)?(?:\\s+([\\w\\d._-]+))?\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s+(.+?)\\s*:\\s(.*)$"); static const Pcre2Regex BufferRegex("^--------- (?:beginning of|switch to) (\\w+)$"); Priority priority_from(char c) { switch (c) { case 'V': return Priority::Verbose; case 'D': return Priority::Debug; case 'I': return Priority::Info; case 'W': return Priority::Warn; case 'E': return Priority::Error; case 'F': return Priority::Fatal; default: return Priority::Unknown; } } const char* priority_to(Priority priority) { switch (priority) { case Priority::Verbose: return "Verbose"; case Priority::Debug: return "Debug"; case Priority::Info: return "Info"; case Priority::Warn: return "Warning"; case Priority::Error: return "Error"; case Priority::Fatal: return "Fatal"; default: return "Unknown"; } } const char* buffer_to(Buffer buffer) { switch (buffer) { case Buffer::Main: return "Main"; case Buffer::System: return "System"; case Buffer::Radio: return "Radio"; case Buffer::Events: return "Events"; case Buffer::Crash: return "Crash"; default: return "Unknown"; } } static inline long to_long(const char* str, const char* expected_end) { char* endptr; errno = 0; long res = strtol(str, &endptr, 10); if (endptr != expected_end) { throw std::invalid_argument(std::string(str) + " has trailing text"); } else if (res == LONG_MAX && errno == ERANGE) { throw std::overflow_error(std::string(str) + " is too big"); } else if (res == LONG_MIN && errno == ERANGE) { throw std::underflow_error(std::string(str) + " is too small"); } return res; } static unsigned long long to_unsigned_long_long(const char* str, const char* expected_end) { char* endptr; errno = 0; unsigned long long res = strtoull(str, &endptr, 10); if (endptr != expected_end) { throw std::invalid_argument(std::string(str) + " has trailing text"); } else if (res == ULLONG_MAX && errno == ERANGE) { throw std::overflow_error(std::string(str) + " is too big"); } return res; } std::optional try_parse_logcat_entry(char* buf, size_t length, Buffer buffer) { regmatch_t matches[8]; matches[0].rm_so = 0; matches[0].rm_eo = static_cast(length); if (static_cast(matches[0].rm_eo) != length) { throw std::range_error("Logcat entry line too long for int"); } if (!LogcatEntryRegex.match(buf, sizeof(matches) / sizeof(regmatch_t), matches, REG_STARTEND)) { return std::nullopt; } std::optional user; if (matches[2].rm_so > -1 && matches[2].rm_eo > -1) { user = std::string(&buf[matches[2].rm_so], static_cast(matches[2].rm_eo - matches[2].rm_so)); } // if pcre2 gives us negative offsets then i'll die LogcatEntry logcat_entry = { .buffer = buffer, .time = to_long(&buf[matches[1].rm_so], &buf[matches[1].rm_eo]), .user = std::move(user), .pid = to_unsigned_long_long(&buf[matches[3].rm_so], &buf[matches[3].rm_eo]), .tid = to_unsigned_long_long(&buf[matches[4].rm_so], &buf[matches[4].rm_eo]), .priority = priority_from(buf[matches[5].rm_so]), .tag = std::string(&buf[matches[6].rm_so], static_cast(matches[6].rm_eo - matches[6].rm_so)), .message = std::string(&buf[matches[7].rm_so], static_cast(matches[7].rm_eo - matches[7].rm_so)), }; return std::move(logcat_entry); } static bool substr_equal(const char* lhs_str, size_t lhs_length, const char* rhs_str) { return lhs_length == strlen(rhs_str) && memcmp(lhs_str, rhs_str, lhs_length * sizeof(char)) == 0; } std::optional try_parse_buffer(char* buf, size_t length) { regmatch_t matches[2]; matches[0].rm_so = 0; matches[0].rm_eo = static_cast(length); if (static_cast(matches[0].rm_eo) != length) { throw std::range_error("Buffer line too long for int"); } if (!BufferRegex.match(buf, sizeof(matches) / sizeof(regmatch_t), matches, REG_STARTEND)) { return std::nullopt; } const char* buffer_str = &buf[matches[1].rm_so]; size_t buffer_length = static_cast(matches[1].rm_eo - matches[1].rm_so); if (substr_equal(buffer_str, buffer_length, "main")) return Buffer::Main; if (substr_equal(buffer_str, buffer_length, "system")) return Buffer::System; if (substr_equal(buffer_str, buffer_length, "radio")) return Buffer::Radio; if (substr_equal(buffer_str, buffer_length, "events")) return Buffer::Events; if (substr_equal(buffer_str, buffer_length, "crash")) return Buffer::Crash; return Buffer::Unknown; }