/* Copyright (C) 2020 C. McEnroe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "daemon.h" #ifndef RUNDIR #define RUNDIR "/var/run" #endif #ifndef ETCDIR #define ETCDIR "/usr/local/etc" #endif #define WS " \t" struct timespec restartInterval = { .tv_sec = 1 }; struct Set256 stopExits; static void configerr(bool exit, const char *format, ...) { va_list ap; va_start(ap, format); if (exit) { verrx(EX_DATAERR, format, ap); } else { vsyslog(LOG_ERR, format, ap); } va_end(ap); } static void parseConfig(bool exit, const char *path) { size_t cap = 0; char *buf = NULL; FILE *file = fopen(path, "r"); if (!file) { configerr(exit, "%s: %s", path, strerror(errno)); goto err; } prependClear(); int line = 1; for (ssize_t len; 0 <= (len = getline(&buf, &cap, file)); ++line) { if (buf[len - 1] == '\n') buf[len - 1] = '\0'; char *ptr = &buf[strspn(buf, WS)]; if (!ptr[0] || ptr[0] == '#') { continue; } else if (ptr[0] == '%') { int error = prependAdd(&ptr[1]); if (error) { configerr( exit, "cannot add prepend command: %s", strerror(errno) ); goto err; } } else { char *name = strsep(&ptr, WS); if (!ptr) { configerr( exit, "%s:%d: no command line for service %s", path, line, name ); goto err; } int error = serviceAdd(name, ptr); if (error) { configerr(exit, "cannot add service: %s", strerror(errno)); goto err; } } } if (ferror(file)) configerr(exit, "%s: %s", path, strerror(errno)); err: free(buf); fclose(file); } static void parseExits(char *list) { setClear(&stopExits); while (*list) { byte exit = strtoul(list, &list, 10); if (*list) { if (*list != ',') errx(EX_USAGE, "invalid exit status %s", list); list++; } setAdd(&stopExits, exit); } } static void parseInterval(const char *millis) { unsigned long ms = strtoul(millis, NULL, 10); restartInterval.tv_sec = ms / 1000; restartInterval.tv_nsec = 1000000 * (ms % 1000); } int main(int argc, char *argv[]) { setprogname(argv[0]); bool daemonize = true; setAdd(&stopExits, EX_USAGE); setAdd(&stopExits, EX_DATAERR); setAdd(&stopExits, EX_NOINPUT); setAdd(&stopExits, EX_OSFILE); setAdd(&stopExits, EX_CANTCREAT); setAdd(&stopExits, EX_CONFIG); const char *pidPath = NULL; const char *configPath = ETCDIR "/spawntab"; const char *fifoPath = RUNDIR "/spawnd.pipe"; const char *userName = NULL; const char *groupName = NULL; for (int opt; 0 < (opt = getopt(argc, argv, "C:c:df:g:p:s:t:u:"));) { switch (opt) { break; case 'C': serviceDir = optarg; break; case 'c': fifoPath = optarg; break; case 'd': daemonize = false; break; case 'f': configPath = optarg; break; case 'g': groupName = optarg; break; case 'p': pidPath = optarg; break; case 's': parseExits(optarg); break; case 't': parseInterval(optarg); break; case 'u': userName = optarg; break; default: return EX_USAGE; } } parseConfig(true, configPath); int error = access(serviceDir, X_OK); if (error) err(EX_NOINPUT, "%s", serviceDir); errno = 0; struct passwd *user = (userName ? getpwnam(userName) : getpwuid(getuid())); if (errno) err(EX_OSFILE, "getpwnam"); if (!user) errx(EX_USAGE, "no such user %s", userName); errno = 0; struct group *group = ( groupName ? getgrnam(groupName) : getgrgid(user->pw_gid) ); if (errno) err(EX_OSFILE, "getgrnam"); if (!group) errx(EX_USAGE, "no such group %s", groupName); serviceUID = user->pw_uid; serviceGID = group->gr_gid; int len = asprintf(&serviceEnviron[LOGNAME], "LOGNAME=%s", user->pw_name); if (len < 0) err(EX_OSERR, "asprintf"); len = asprintf(&serviceEnviron[USER], "USER=%s", user->pw_name); if (len < 0) err(EX_OSERR, "asprintf"); len = asprintf(&serviceEnviron[HOME], "HOME=%s", user->pw_dir); if (len < 0) err(EX_OSERR, "asprintf"); int pidFile = -1; if (pidPath) { pidFile = open( pidPath, O_WRONLY | O_CREAT | O_EXLOCK | O_CLOEXEC, 0600 ); if (pidFile < 0) err(EX_CANTCREAT, "%s", pidPath); } // We can't lock a named pipe, so just warn if it already exists. error = mkfifo(fifoPath, 0600); if (error) { if (errno != EEXIST) err(EX_CANTCREAT, "%s", fifoPath); warn("%s", fifoPath); } int fifo = open(fifoPath, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (fifo < 0) err(EX_CANTCREAT, "%s", fifoPath); openlog(getprogname(), LOG_NDELAY | LOG_PID | LOG_PERROR, LOG_DAEMON); if (daemonize) { error = daemon(0, 0); if (error) { syslog(LOG_ERR, "daemon: %m"); return EX_OSERR; } } if (pidPath) { int len = dprintf(pidFile, "%ju", (uintmax_t)getpid()); if (len < 0) syslog(LOG_WARNING, "%s: %m", pidPath); } // TODO: Main loop. close(fifo); unlink(fifoPath); if (pidPath) { close(pidFile); unlink(pidPath); } }