summaryrefslogtreecommitdiff
path: root/src/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c240
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;
+}