#include #include #include #include #include "config.h" #include "logcat_entry.h" #include "pcre2_wrapper.h" static const Pcre2Regex LogcatEntryRegex( "^ *(?:(\\d+)|(\\d{4,})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}))(?:\\.\\d+)?" // time and date "(?: +([\\w\\d._-]+))?" // optional user " +(\\d+)" // pid " +(\\d+)" // tid " +([A-Z])" // priority " +(.+?) *: (.*)$" // tag and message ); static const Pcre2Regex BufferRegex("^--------- (?:beginning of|switch to) (\\w+)$"); static std::string leftpad(const std::string& str, size_t characters); static inline char to_char(Priority priority); static inline std::string rightpad(const std::string& str, size_t characters); static inline Priority priority_from(char c); static inline int to_int(const char* str, const char* expected_end); static inline long to_long(const char* str, const char* expected_end); static unsigned long long to_ull(const char* str, const char* expected_end); const char* to_string(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"; case Priority::Unknown: return "Unknown"; } return "Unknown??"; } const char* to_string(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"; case Buffer::Unknown: return "Unknown"; } return "Unknown??"; } const char* to_string_lower(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"; case Buffer::Unknown: return "unknown"; } return "Unknown??"; } std::string to_string(const LogcatEntry& logcat_entry) { struct tm tm = logcat_entry.time; time_t time = mktime(&tm); gmtime_r(&time, &tm); char logcat_style_time_as_str[32] = {0}; strftime(logcat_style_time_as_str, 31 * sizeof(char), "%Y-%m-%d %H:%M:%S", &tm); return std::string(logcat_style_time_as_str) + ' ' + leftpad(logcat_entry.user.value_or(" "), 5) + ' ' + leftpad(std::to_string(logcat_entry.pid), 5) + ' ' + leftpad(std::to_string(logcat_entry.tid), 5) + ' ' + to_char(logcat_entry.priority) + ' ' + rightpad(logcat_entry.tag, 5) + ": " + logcat_entry.message; } std::optional try_parse_logcat_entry(char* buf, size_t length, Buffer buffer) { regmatch_t matches[14]; 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; } auto group_exists = [](const regmatch_t& match) { return match.rm_so > -1 && match.rm_eo > -1; }; auto group_int = [&](const regmatch_t& match) { return to_int(&buf[match.rm_so], &buf[match.rm_eo]); }; auto group_long = [&](const regmatch_t& match) { return to_long(&buf[match.rm_so], &buf[match.rm_eo]); }; auto group_ull = [&](const regmatch_t& match) { return to_ull(&buf[match.rm_so], &buf[match.rm_eo]); }; auto group_string = [&](const regmatch_t& match) { return std::string(&buf[match.rm_so], static_cast(match.rm_eo - match.rm_so)); }; auto group_priority = [&](const regmatch_t& match) { return priority_from(buf[match.rm_so]); }; // if pcre2 gives us negative offsets then i'll die LogcatEntry logcat_entry = { .buffer = buffer, // time to be set later .user = group_exists(matches[8]) ? std::optional(group_string(matches[8])) : std::nullopt, .pid = group_ull(matches[9]), .tid = group_ull(matches[10]), .priority = group_priority(matches[11]), .tag = group_string(matches[12]), .message = group_string(matches[13]), }; if (group_exists(matches[1])) { time_t time = group_long(matches[1]); localtime_r(&time, &logcat_entry.time); } else { struct tm tm = { .tm_sec = group_int(matches[7]), .tm_min = group_int(matches[6]), .tm_hour = group_int(matches[5]), .tm_mday = group_int(matches[4]), .tm_mon = group_int(matches[3]), .tm_year = group_int(matches[2]) - 1900, .tm_isdst = -1, .tm_gmtoff = 0, }; time_t time = mktime(&tm) + tm.tm_gmtoff; // for some reason you have to add the local time offset? localtime_r(&time, &logcat_entry.time); } return logcat_entry; } 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]; if (!strncmp(buffer_str, "main", 4)) return Buffer::Main; if (!strncmp(buffer_str, "system", 6)) return Buffer::System; if (!strncmp(buffer_str, "radio", 5)) return Buffer::Radio; if (!strncmp(buffer_str, "events", 6)) return Buffer::Events; if (!strncmp(buffer_str, "crash", 5)) return Buffer::Crash; return Buffer::Unknown; } void LogcatEntries::push_back(LogcatEntry entry) { this->_logcat_entries.push_back(std::move(entry)); if (matches(this->_logcat_entries.back(), this->_config.filters, this->_config.exclusions)) { this->_filtered_offsets.push_back(this->_logcat_entries.size() - 1); } } void LogcatEntries::refresh() { this->_filtered_offsets.clear(); for (size_t i = 0; i < this->_logcat_entries.size(); i++) { if (matches(this->_logcat_entries[i], this->_config.filters, this->_config.exclusions)) { this->_filtered_offsets.push_back(i); } } } void from_json(const nlohmann::json& j, Buffer& buffer) { const std::string& str = j.get_ref(); if (str == "UNKNOWN") buffer = Buffer::Unknown; else if (str == "MAIN") buffer = Buffer::Main; else if (str == "SYSTEM") buffer = Buffer::System; else if (str == "RADIO") buffer = Buffer::Radio; else if (str == "EVENTS") buffer = Buffer::Events; else if (str == "CRASH") buffer = Buffer::Crash; else throw std::invalid_argument(std::string("Unknown buffer value: ") + str); } void to_json(nlohmann::json& j, const Buffer& buffer) { switch (buffer) { case Buffer::Unknown: j = "UNKNOWN"; break; case Buffer::Main: j = "MAIN"; break; case Buffer::System: j = "SYSTEM"; break; case Buffer::Radio: j = "RADIO"; break; case Buffer::Events: j = "EVENTS"; break; case Buffer::Crash: j = "CRASH"; break; } } void from_json(const nlohmann::json& j, Priority& priority) { const std::string& str = j.get_ref(); if (str == "UNKNOWN") priority = Priority::Unknown; else if (str == "VERBOSE") priority = Priority::Verbose; else if (str == "DEBUG") priority = Priority::Debug; else if (str == "INFO") priority = Priority::Info; else if (str == "WARN") priority = Priority::Warn; else if (str == "ERROR") priority = Priority::Error; else if (str == "FATAL") priority = Priority::Fatal; else throw std::invalid_argument(std::string("Unknown priority value: ") + str); } void to_json(nlohmann::json& j, const Priority& priority) { switch (priority) { case Priority::Unknown: j = "UNKNOWN"; break; case Priority::Verbose: j = "VERBOSE"; break; case Priority::Debug: j = "DEBUG"; break; case Priority::Info: j = "INFO"; break; case Priority::Warn: j = "WARN"; break; case Priority::Error: j = "ERROR"; break; case Priority::Fatal: j = "FATAL"; break; } } static std::string leftpad(const std::string& str, size_t characters) { return str.size() >= characters ? str : std::string(characters - str.size(), ' ') + str; } static inline char to_char(Priority priority) { switch (priority) { case Priority::Verbose: return 'V'; case Priority::Debug: return 'D'; case Priority::Info: return 'I'; case Priority::Warn: return 'W'; case Priority::Error: return 'E'; case Priority::Fatal: return 'F'; case Priority::Unknown: return 'U'; } return 'U'; } static inline std::string rightpad(const std::string& str, size_t characters) { return str.size() >= characters ? str : str + std::string(characters - str.size(), ' '); } static inline 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; } } static inline int to_int(const char* str, const char* expected_end) { long res = to_long(str, expected_end); if (res > INT_MAX) { throw std::overflow_error(std::string(str) + " is too big"); } else if (res < INT_MIN) { throw std::underflow_error(std::string(str) + " is too small"); } return static_cast(res); } 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_ull(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; }