#include "log_tail.h" #include "client.h" #include "unit.h" #include "log.h" #include #include #include #include #include #include #include #include #define MAX_LOG_WATCHES (MAX_UNITS * 4) typedef struct { int inotify_fd; int watch_wd; int log_fd; Unit *unit; char path[MAX_PATH]; } LogWatch; static LogWatch watches[MAX_LOG_WATCHES]; static int watch_count = 0; void log_tail_init(void) { watch_count = 0; log_info("log_tail_init: checking %d unit(s) for log paths", g.unit_count); for (int i = 0; i < g.unit_count; i++) { Unit *u = &g.units[i]; log_info("log_tail_init: unit '%s' has %d log path(s)", u->name, u->log_count); for (int j = 0; j < u->log_count; j++) { const char *path = u->log_paths[j]; if (!path[0]) continue; int log_fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (log_fd < 0) { log_warn("log_tail: cannot open %s: %s", path, strerror(errno)); continue; } lseek(log_fd, 0, SEEK_END); int ifd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (ifd < 0) { log_error("inotify_init1: %s", strerror(errno)); close(log_fd); continue; } int wd = inotify_add_watch(ifd, path, IN_MODIFY); if (wd < 0) { log_warn("log_tail: inotify_add_watch %s: %s", path, strerror(errno)); close(ifd); close(log_fd); continue; } if (watch_count >= MAX_LOG_WATCHES) { log_warn("log_tail: max watches reached, skipping %s", path); close(ifd); close(log_fd); continue; } watches[watch_count].inotify_fd = ifd; watches[watch_count].watch_wd = wd; watches[watch_count].log_fd = log_fd; watches[watch_count].unit = u; strncpy(watches[watch_count].path, path, MAX_PATH - 1); watch_count++; struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = ifd; epoll_ctl(g.epoll_fd, EPOLL_CTL_ADD, ifd, &ev); log_info("log_tail: watching %s for unit %s", path, u->name); } } } Unit *log_tail_fd_to_unit(int fd) { for (int i = 0; i < watch_count; i++) { if (watches[i].inotify_fd == fd) return watches[i].unit; } return NULL; } static LogWatch *watch_for_inotify_fd(int fd) { for (int i = 0; i < watch_count; i++) { if (watches[i].inotify_fd == fd) return &watches[i]; } return NULL; } void log_tail_handle(int inotify_fd) { LogWatch *w = watch_for_inotify_fd(inotify_fd); if (!w) return; char evbuf[4096]; while (read(inotify_fd, evbuf, sizeof(evbuf)) > 0) ; char buf[RING_BUF_LINE_MAX]; ssize_t n; while ((n = read(w->log_fd, buf, sizeof(buf) - 1)) > 0) { buf[n] = '\0'; ring_push(w->unit->output, buf, (int)n); client_broadcast_output(w->unit->name, buf, 0); } } void log_tail_cleanup(void) { for (int i = 0; i < watch_count; i++) { epoll_ctl(g.epoll_fd, EPOLL_CTL_DEL, watches[i].inotify_fd, NULL); inotify_rm_watch(watches[i].inotify_fd, watches[i].watch_wd); close(watches[i].inotify_fd); close(watches[i].log_fd); } watch_count = 0; }