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/daemon.c | |
Add Umbrella 0.1.5
Diffstat (limited to 'src/daemon.c')
| -rw-r--r-- | src/daemon.c | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/src/daemon.c b/src/daemon.c new file mode 100644 index 0000000..9714a65 --- /dev/null +++ b/src/daemon.c @@ -0,0 +1,148 @@ +//#include "daemon.h" +#include "umbrella.h" +#include "log.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/signalfd.h> + +/* + * Create a directory and all parents, like mkdir -p. + * Returns 0 if the directory exists or was created, -1 on error. + */ +static int mkdirp(const char *path, mode_t mode) { + char tmp[MAX_PATH]; + char *p; + size_t len; + + snprintf(tmp, sizeof(tmp), "%s", path); + len = strlen(tmp); + if (tmp[len - 1] == '/') + tmp[len - 1] = '\0'; + + for (p = tmp + 1; *p; p++) { + if (*p == '/') { + *p = '\0'; + if (mkdir(tmp, mode) != 0 && errno != EEXIST) + return -1; + *p = '/'; + } + } + if (mkdir(tmp, mode) != 0 && errno != EEXIST) + return -1; + return 0; +} + +int daemon_daemonize(int foreground) { + if (foreground) + return 0; + + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid > 0) + exit(0); /* parent exits */ + + /* Child: become session leader */ + if (setsid() < 0) { + perror("setsid"); + return -1; + } + + /* Fork again so we're not a session leader (can't acquire a tty) */ + pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid > 0) + exit(0); + + /* Redirect stdin/stdout/stderr to /dev/null */ + int dev_null = open("/dev/null", O_RDWR); + if (dev_null >= 0) { + dup2(dev_null, STDIN_FILENO); + dup2(dev_null, STDOUT_FILENO); + dup2(dev_null, STDERR_FILENO); + if (dev_null > STDERR_FILENO) + close(dev_null); + } + + umask(027); + return 0; +} + +int daemon_write_pid(void) { + /* Ensure the run directory exists */ + if (mkdirp("/run/umbrella", 0755) != 0 && errno != EEXIST) { + log_error("Could not create /run/umbrella: %s", strerror(errno)); + return -1; + } + + FILE *f = fopen(UMBRELLA_PID_FILE, "w"); + if (!f) { + log_error("Could not write pid file %s: %s", + UMBRELLA_PID_FILE, strerror(errno)); + return -1; + } + fprintf(f, "%d\n", (int)getpid()); + fclose(f); + return 0; +} + +int daemon_init(void) { + /* Ensure runtime and log directories exist */ + if (mkdirp("/run/umbrella", 0755) != 0 && errno != EEXIST) { + log_error("mkdirp /run/umbrella: %s", strerror(errno)); + return -1; + } + if (mkdirp("/var/log/umbrella", 0755) != 0 && errno != EEXIST) { + log_error("mkdirp /var/log/umbrella: %s", strerror(errno)); + return -1; + } + + if (daemon_write_pid() != 0) + return -1; + + /* + * Set up a signalfd so signals arrive as readable events on the + * epoll loop rather than interrupting syscalls unpredictably. + * We block the signals first, then read them via the fd. + */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGCHLD); /* child process state changes */ + sigaddset(&mask, SIGHUP); /* reload config */ + sigaddset(&mask, SIGPIPE); /* ignore broken pipe */ + + if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0) { + log_error("sigprocmask: %s", strerror(errno)); + return -1; + } + + int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (sfd < 0) { + log_error("signalfd: %s", strerror(errno)); + return -1; + } + + g.signal_fd = sfd; + log_info("Daemon initialized (pid %d)", (int)getpid()); + return 0; +} + +void daemon_cleanup(void) { + log_info("Cleaning up..."); + unlink(UMBRELLA_PID_FILE); + unlink(UMBRELLA_SOCK_PATH); +} |
