summaryrefslogtreecommitdiff
path: root/src/log_tail.c
diff options
context:
space:
mode:
authorauric <auric@japegames.com>2026-02-21 11:08:36 -0600
committerauric <auric@japegames.com>2026-02-21 11:08:36 -0600
commit0d706ae72ceefd74053ad6cb0900ecce6cf1f085 (patch)
tree6faf7d3919182b8838a6ae69ad1a2a0fac468740 /src/log_tail.c
Add Umbrella 0.1.5
Diffstat (limited to 'src/log_tail.c')
-rw-r--r--src/log_tail.c118
1 files changed, 118 insertions, 0 deletions
diff --git a/src/log_tail.c b/src/log_tail.c
new file mode 100644
index 0000000..d352f8f
--- /dev/null
+++ b/src/log_tail.c
@@ -0,0 +1,118 @@
+#include "log_tail.h"
+#include "client.h"
+#include "unit.h"
+#include "log.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/inotify.h>
+
+#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;
+}