249 lines
8.2 KiB
C++
249 lines
8.2 KiB
C++
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/epoll.h>
|
|
#include <system_error>
|
|
|
|
#include "log.h"
|
|
#include "misc.h"
|
|
#include "logcat_thread.h"
|
|
#include "logcat_entry.h"
|
|
|
|
#define EPOLL_MAX_EVENTS 10
|
|
|
|
static void mark_nonblock(int fd) {
|
|
int flags = fcntl(fd, F_GETFL);
|
|
if (flags < 0) {
|
|
throw_system_error("fcntl(fd, F_GETFL)");
|
|
}
|
|
if (!(flags & O_NONBLOCK) && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
|
throw_system_error("fcntl(fd, F_SETFL)");
|
|
}
|
|
}
|
|
|
|
static inline void look_for_newlines(char buf[NEWLINE_BUF_SIZE], size_t* used, size_t previous_used,
|
|
void(LogcatThread::*handle_line)(char*, size_t, bool), LogcatThread* logcat_thread, bool is_stdout) {
|
|
size_t search_offset = previous_used;
|
|
size_t real_offset = 0;
|
|
|
|
while (*used > search_offset) {
|
|
char* newline_ptr = static_cast<char*>(memchr(&buf[search_offset], '\n', (*used - search_offset) * sizeof(char)));
|
|
if (!newline_ptr) {
|
|
break;
|
|
}
|
|
|
|
size_t newline_length = (reinterpret_cast<size_t>(newline_ptr) - reinterpret_cast<size_t>(&buf[real_offset])) / sizeof(char);
|
|
(logcat_thread->*handle_line)(&buf[real_offset], newline_length, is_stdout);
|
|
|
|
real_offset += newline_length + 1;
|
|
search_offset = real_offset;
|
|
}
|
|
|
|
if (real_offset) {
|
|
if (*used > search_offset) {
|
|
memcpy(buf, &buf[real_offset], (*used - real_offset) * sizeof(char));
|
|
*used -= real_offset;
|
|
} else {
|
|
*used = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void handle_fd(int fd, char* buf, size_t* used,
|
|
void(LogcatThread::*handle_line)(char*, size_t, bool), LogcatThread* logcat_thread, bool is_stdout) {
|
|
while (true) {
|
|
ssize_t read_size_u = read(fd, &buf[*used], (NEWLINE_BUF_SIZE - *used) * sizeof(char));
|
|
if (read_size_u == 0) {
|
|
return;
|
|
} else if (read_size_u < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
|
|
return;
|
|
} else if (read_size_u < 0) {
|
|
throw_system_error("read()");
|
|
}
|
|
size_t read_size = static_cast<size_t>(read_size_u) / sizeof(char);
|
|
if (*used + read_size > NEWLINE_BUF_SIZE) {
|
|
throw std::runtime_error("Received line longer than 512k");
|
|
}
|
|
size_t previous_used = *used;
|
|
*used += read_size;
|
|
|
|
look_for_newlines(buf, used, previous_used, handle_line, logcat_thread, is_stdout);
|
|
}
|
|
}
|
|
|
|
|
|
LogcatThread::LogcatThread() {
|
|
int fds[2];
|
|
struct epoll_event event = {.events = EPOLLIN | EPOLLET};
|
|
|
|
if (pipe(fds)) {
|
|
int errsv = errno;
|
|
this->~LogcatThread();
|
|
throw_system_error(errsv, "pipe() for stdout");
|
|
}
|
|
this->_stdout_read_fd = fds[0];
|
|
this->_stdout_write_fd = fds[1];
|
|
try {
|
|
mark_nonblock(this->_stdout_read_fd);
|
|
} catch (const std::exception& e) {
|
|
this->~LogcatThread();
|
|
throw;
|
|
}
|
|
|
|
if (pipe(fds)) {
|
|
int errsv = errno;
|
|
this->~LogcatThread();
|
|
throw_system_error(errsv, "pipe() for stderr");
|
|
}
|
|
this->_stderr_read_fd = fds[0];
|
|
this->_stderr_write_fd = fds[1];
|
|
try {
|
|
mark_nonblock(this->_stderr_read_fd);
|
|
} catch (const std::exception& e) {
|
|
this->~LogcatThread();
|
|
throw;
|
|
}
|
|
|
|
this->_epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
|
if (this->_epoll_fd == -1) {
|
|
int errsv = errno;
|
|
this->~LogcatThread();
|
|
throw_system_error(errsv, "epoll_create1()");
|
|
}
|
|
event.data.fd = this->_stdout_read_fd;
|
|
if (epoll_ctl(this->_epoll_fd, EPOLL_CTL_ADD, this->_stdout_read_fd, &event)) {
|
|
int errsv = errno;
|
|
this->~LogcatThread();
|
|
throw_system_error(errsv, "epoll_ctl() for stdout");
|
|
}
|
|
event.data.fd = this->_stderr_read_fd;
|
|
if (epoll_ctl(this->_epoll_fd, EPOLL_CTL_ADD, this->_stderr_read_fd, &event)) {
|
|
int errsv = errno;
|
|
this->~LogcatThread();
|
|
throw_system_error(errsv, "epoll_ctl() for stderr");
|
|
}
|
|
|
|
this->_thread = std::thread(&LogcatThread::_run, this, this->_stop_source.get_token());
|
|
}
|
|
|
|
LogcatThread::~LogcatThread() {
|
|
if (this->_epoll_fd != -1 && close(this->_epoll_fd)) {
|
|
log(std::string("Failed to close epoll file descriptor: close(): ") + strerror(errno));
|
|
}
|
|
if (this->_stdout_read_fd != -1 && close(this->_stdout_read_fd)) {
|
|
log(std::string("Failed to close stdout read pipe: close(): ") + strerror(errno));
|
|
}
|
|
if (this->_stdout_write_fd != -1 && close(this->_stdout_write_fd)) {
|
|
log(std::string("Failed to close stdout write pipe: close(): ") + strerror(errno));
|
|
}
|
|
if (this->_stderr_read_fd != -1 && close(this->_stderr_read_fd)) {
|
|
log(std::string("Failed to close stderr read pipe: close(): ") + strerror(errno));
|
|
}
|
|
if (this->_stderr_write_fd != -1 && close(this->_stderr_write_fd)) {
|
|
log(std::string("Failed to close stderr write pipe: close(): ") + strerror(errno));
|
|
}
|
|
}
|
|
|
|
void LogcatThread::request_stop() {
|
|
this->_stop_source.request_stop();
|
|
}
|
|
|
|
void LogcatThread::join() {
|
|
this->_thread.join();
|
|
}
|
|
|
|
void LogcatThread::_put_if_not_stopped(LogcatThreadItem item) {
|
|
while (true) {
|
|
if (this->atomic_ring_buffer.try_put_and_increment_write(item)) {
|
|
break;
|
|
}
|
|
#ifndef NDEBUG
|
|
printf("spinlocking!!!\n");
|
|
#endif
|
|
if (this->_stop_source.stop_requested()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LogcatThread::_handle_line(char* buf, size_t length, bool is_stdout) {
|
|
if (!is_stdout) {
|
|
LogEntry log_entry = {time(nullptr), std::string("Received from logcat stderr: ") + std::string(buf, length)};
|
|
print_log(log_entry);
|
|
this->_put_if_not_stopped(std::move(log_entry));
|
|
return;
|
|
}
|
|
|
|
std::optional<LogcatEntry> logcat_entry;
|
|
try {
|
|
logcat_entry = try_parse_logcat_entry(buf, length, this->_current_buffer);
|
|
} catch (const std::exception& e) {
|
|
LogEntry log_entry = {time(nullptr), std::string("Failed to parse logcat entry: ") + e.what()};
|
|
print_log(log_entry);
|
|
this->_put_if_not_stopped(std::move(log_entry));
|
|
}
|
|
if (logcat_entry) {
|
|
this->_put_if_not_stopped(std::move(*logcat_entry));
|
|
return;
|
|
}
|
|
std::optional<Buffer> new_buffer;
|
|
try {
|
|
new_buffer = try_parse_buffer(buf, length);
|
|
} catch (const std::exception& e) {
|
|
LogEntry log_entry = {time(nullptr), std::string("Failed to parse buffer line: ") + e.what()};
|
|
print_log(log_entry);
|
|
this->_put_if_not_stopped(std::move(log_entry));
|
|
}
|
|
if (new_buffer) {
|
|
this->_current_buffer = *new_buffer;
|
|
return;
|
|
}
|
|
|
|
LogEntry log_entry = {time(nullptr), std::string("Cannot parse logcat stdout: ") + std::string(buf, length)};
|
|
print_log(log_entry);
|
|
this->_put_if_not_stopped(std::move(log_entry));
|
|
}
|
|
|
|
void LogcatThread::_run_epoll_round() {
|
|
struct epoll_event events[EPOLL_MAX_EVENTS];
|
|
|
|
int ready_fds = epoll_wait(this->_epoll_fd, events, EPOLL_MAX_EVENTS, 1000);
|
|
if (ready_fds == -1) {
|
|
LogEntry log_entry = {time(nullptr), std::string("epoll_wait(): ") + strerror(errno)};
|
|
print_log(log_entry);
|
|
this->_put_if_not_stopped(std::move(log_entry));
|
|
return;
|
|
}
|
|
|
|
for (int i=0; i < ready_fds; i++) {
|
|
const bool is_stdout = events[i].data.fd == this->_stdout_read_fd;
|
|
char* buf = is_stdout ? this->_stdout_buf : this->_stderr_buf;
|
|
size_t* used = is_stdout ? &this->_stdout_buf_used : &this->_stderr_buf_used;
|
|
|
|
try {
|
|
handle_fd(events[i].data.fd, buf, used, &LogcatThread::_handle_line, this, is_stdout);
|
|
} catch (const std::exception& e) {
|
|
std::string message = "Failed to handle std";
|
|
message += is_stdout ? "out: " : "err: ";
|
|
message += e.what();
|
|
LogEntry log_entry = {time(nullptr), std::move(message)};
|
|
print_log(log_entry);
|
|
this->_put_if_not_stopped(std::move(log_entry));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LogcatThread::_run(std::stop_token stoken) {
|
|
while (!stoken.stop_requested()) {
|
|
#ifndef NDEBUG
|
|
if (this->debug_log_request.test()) {
|
|
LogEntry log_entry = {time(nullptr), "A log entry from the logcat thread :D"};
|
|
this->_put_if_not_stopped(std::move(log_entry));
|
|
this->debug_log_request.clear();
|
|
}
|
|
#endif
|
|
|
|
this->_run_epoll_round();
|
|
}
|
|
}
|