diff options
| author | auric <auric@japegames.com> | 2026-02-21 11:08:36 -0600 |
|---|---|---|
| committer | auric <auric@japegames.com> | 2026-02-21 11:08:36 -0600 |
| commit | 0d706ae72ceefd74053ad6cb0900ecce6cf1f085 (patch) | |
| tree | 6faf7d3919182b8838a6ae69ad1a2a0fac468740 /src/main.c | |
Add Umbrella 0.1.5
Diffstat (limited to 'src/main.c')
| -rw-r--r-- | src/main.c | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..f242489 --- /dev/null +++ b/src/main.c @@ -0,0 +1,240 @@ +#include "umbrella.h" +#include "daemon.h" +#include "unit.h" +#include "client.h" +#include "log.h" +#include "log_tail.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <sys/epoll.h> +#include <sys/signalfd.h> +#include <sys/wait.h> + +/* Global daemon state — defined here, declared extern in umbrella.h */ +Daemon g; + +/* ── Signal handling ─────────────────────────────────────────────────────── */ + +static void handle_signal(void) { + struct signalfd_siginfo info; + ssize_t n = read(g.signal_fd, &info, sizeof(info)); + if (n != sizeof(info)) return; + + switch (info.ssi_signo) { + case SIGTERM: + case SIGINT: + log_info("Received signal %d — shutting down", info.ssi_signo); + g.running = 0; + break; + + case SIGCHLD: + /* Reap all finished child processes (action scripts, etc.) */ + while (1) { + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) break; + + /* Check if any managed STDIN unit matches this pid */ + for (int i = 0; i < g.unit_count; i++) { + Unit *u = &g.units[i]; + if (u->console.type == CONSOLE_STDIN && u->pid == pid) { + if (WIFEXITED(status)) + log_warn("Unit %s exited with code %d", + u->name, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + log_warn("Unit %s killed by signal %d", + u->name, WTERMSIG(status)); + u->state = STATE_CRASHED; + u->pid = 0; + break; + } + } + } + break; + + case SIGHUP: + log_info("SIGHUP received — reloading units"); + /* Reload unit descriptors. Existing runtime state is preserved. */ + g.unit_count = 0; + unit_load_all(); + log_tail_init(); + break; + } +} + +/* ── Process stdout handling ─────────────────────────────────────────────── */ +/* + * Called when a STDIN-type unit's stdout fd is readable. + * Reads available output, pushes to ring buffer, broadcasts to clients. + */ +static void handle_process_output(int fd) { + /* Find which unit owns this fd */ + Unit *u = NULL; + for (int i = 0; i < g.unit_count; i++) { + if (g.units[i].stdout_fd == fd) { + u = &g.units[i]; + break; + } + } + if (!u) return; + + char buf[RING_BUF_LINE_MAX]; + ssize_t n = read(fd, buf, sizeof(buf) - 1); + if (n <= 0) { + /* Process closed its stdout — mark as stopped */ + log_info("Unit %s stdout closed", u->name); + epoll_ctl(g.epoll_fd, EPOLL_CTL_DEL, fd, NULL); + close(fd); + u->stdout_fd = -1; + return; + } + + buf[n] = '\0'; + ring_push(u->output, buf, n); + client_broadcast_output(u->name, buf, 0); +} + +/* ── Main event loop ─────────────────────────────────────────────────────── */ + +static void event_loop(void) { + struct epoll_event events[64]; + + while (g.running) { + int n = epoll_wait(g.epoll_fd, events, 64, -1); + if (n < 0) { + if (errno == EINTR) continue; + log_error("epoll_wait: %s", strerror(errno)); + break; + } + + for (int i = 0; i < n; i++) { + int fd = events[i].data.fd; + uint32_t ev = events[i].events; + + if (fd == g.signal_fd) { + handle_signal(); + + } else if (fd == g.listen_fd) { + client_accept(g.listen_fd); + + } else if (log_tail_fd_to_unit(fd) != NULL) { + log_tail_handle(fd); + + } else { + /* Check if this is a unit stdout fd */ + int is_unit_stdout = 0; + for (int j = 0; j < g.unit_count; j++) { + if (g.units[j].stdout_fd == fd) { + is_unit_stdout = 1; + break; + } + } + + if (is_unit_stdout) { + handle_process_output(fd); + } else { + /* It's a client fd */ + if (ev & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { + client_remove(fd); + } else if (ev & EPOLLIN) { + if (client_handle(fd) < 0) + client_remove(fd); + } + } + } + } + } +} + +/* ── Entry point ─────────────────────────────────────────────────────────── */ + +static void usage(const char *prog) { + fprintf(stderr, + "Usage: %s [options]\n" + " -f Run in foreground (don't daemonize)\n" + " -d Enable debug logging\n" + " -h Show this help\n", + prog); +} + +int main(int argc, char *argv[]) { + int foreground = 0; + int debug = 0; + int opt; + + while ((opt = getopt(argc, argv, "fdh")) != -1) { + switch (opt) { + case 'f': foreground = 1; break; + case 'd': debug = 1; break; + case 'h': usage(argv[0]); return 0; + default: usage(argv[0]); return 1; + } + } + + /* Initialize logging before anything else */ + log_init(foreground ? NULL : UMBRELLA_LOG_FILE, + debug ? LOG_DEBUG : LOG_INFO); + log_info("umbrella %s starting", UMBRELLA_VERSION); + + /* Daemonize if not running in foreground */ + if (daemon_daemonize(foreground) != 0) { + log_error("daemonize failed"); + return 1; + } + + /* Set up epoll */ + g.epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (g.epoll_fd < 0) { + log_error("epoll_create1: %s", strerror(errno)); + return 1; + } + + /* Initialize daemon (signals, pid file, directories) */ + if (daemon_init() != 0) { + log_error("daemon_init failed"); + return 1; + } + + /* Register signal fd with epoll */ + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = g.signal_fd; + epoll_ctl(g.epoll_fd, EPOLL_CTL_ADD, g.signal_fd, &ev); + + log_tail_init(); + + /* Load unit descriptors */ + if (unit_load_all() < 0) { + log_error("Failed to load units"); + daemon_cleanup(); + return 1; + } + + /* Set up listening socket */ + g.listen_fd = client_listen(); + if (g.listen_fd < 0) { + log_error("Failed to create listen socket"); + daemon_cleanup(); + return 1; + } + + ev.events = EPOLLIN; + ev.data.fd = g.listen_fd; + epoll_ctl(g.epoll_fd, EPOLL_CTL_ADD, g.listen_fd, &ev); + + log_info("Ready. %d unit(s) loaded.", g.unit_count); + g.running = 1; + + event_loop(); + + log_info("Shutting down"); + log_tail_cleanup(); + daemon_cleanup(); + log_close(); + return 0; +} |
