#include "umbrella.h" #include "daemon.h" #include "unit.h" #include "client.h" #include "log.h" #include "log_tail.h" #include #include #include #include #include #include #include #include #include /* 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"); log_tail_cleanup(); /* 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); /* Load unit descriptors */ if (unit_load_all() < 0) { log_error("Failed to load units"); daemon_cleanup(); return 1; } log_tail_init(); /* 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; }