/* 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 "daemon.h" const char *serviceDir = "/"; uid_t serviceUID; gid_t serviceGID; char *serviceEnviron[EnvironLen] = { [SHELL] = "SHELL=" _PATH_BSHELL, [PATH] = "PATH=" _PATH_DEFPATH, }; struct Prepend prepend; struct Services services; static void serviceFree(struct Service *service) { free(service->name); free(service->command); if (service->outPipe[0] >= 0) { close(service->outPipe[0]); close(service->outPipe[1]); } if (service->errPipe[0] >= 0) { close(service->errPipe[0]); close(service->errPipe[1]); } } int serviceAdd(const char *name, const char *command) { struct Service *service = NULL; for (size_t i = 0; i < services.len; ++i) { if (!strcmp(services.ptr[i].name, name)) { service = &services.ptr[i]; break; } } if (service) { char *dup = strdup(command); if (!dup) return -1; free(service->command); service->command = dup; return 0; } if (services.len == services.cap) { size_t cap = (services.cap ? services.cap * 2 : 8); void *ptr = realloc(services.ptr, sizeof(*services.ptr) * cap); if (!ptr) return -1; services.cap = cap; services.ptr = ptr; } service = &services.ptr[services.len]; *service = (struct Service) { .outPipe = { -1, -1 }, .errPipe = { -1, -1 }, .restartInterval = restartInterval, }; service->name = strdup(name); if (!service->name) goto err; service->command = strdup(command); if (!service->command) goto err; int error = pipe2(service->outPipe, O_CLOEXEC); if (error) goto err; error = pipe2(service->errPipe, O_CLOEXEC); if (error) goto err; fcntl(service->outPipe[0], F_SETFL, O_NONBLOCK); fcntl(service->errPipe[0], F_SETFL, O_NONBLOCK); services.len++; return 0; err: serviceFree(service); return -1; } void serviceStart(struct Service *service) { if (service->state != Stop) return; if (service->intent == Start) { struct timespec backoff = service->restartInterval; timespecadd(&backoff, &backoff, &service->restartInterval); } else { service->restartInterval = restartInterval; } clock_gettime(CLOCK_MONOTONIC_FAST, &service->restartDeadline); timespecadd( &service->restartDeadline, &service->restartInterval, &service->restartDeadline ); service->intent = Start; service->pid = fork(); if (service->pid < 0) { syslog(LOG_ERR, "fork: %m"); return; } if (service->pid) { service->state = Start; return; } closelog(); dup2(service->outPipe[1], STDOUT_FILENO); dup2(service->errPipe[1], STDERR_FILENO); int error = chdir(serviceDir); if (error) err(StopExit, "%s", serviceDir); error = setgid(serviceGID); if (error) err(StopExit, "setgid"); error = setuid(serviceUID); if (error) err(StopExit, "setuid"); size_t len = 0; char command[ARG_MAX]; for (size_t i = 0; i < prepend.len; ++i) { int n = snprintf( &command[len], sizeof(command) - len, "%s;", prepend.commands[i] ); assert(n > 0); len += n; } snprintf(&command[len], sizeof(command) - len, "exec %s", service->command); execle( _PATH_BSHELL, _PATH_BSHELL, "-c", command, service->name, NULL, serviceEnviron ); err(StopExit, "%s", _PATH_BSHELL); } void serviceSignal(struct Service *service, int signal) { if (service->state != Start) return; int error = kill(service->pid, signal); if (error) { syslog( LOG_ERR, "signal %s %s[%ju]: %m", sys_signame[signal], service->name, (uintmax_t)service->pid ); } } void serviceStop(struct Service *service) { service->intent = Stop; serviceSignal(service, SIGTERM); } void serviceRestart(struct Service *service) { if (service->state == Start) { service->intent = Restart; serviceSignal(service, SIGTERM); } else { serviceStart(service); } }