diff options
Diffstat (limited to 'src/main.c')
-rw-r--r-- | src/main.c | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..3d38ec7 --- /dev/null +++ b/src/main.c @@ -0,0 +1,297 @@ +/* This file is part of Ping903 + Copyright (C) 2020 Sergey Poznyakoff + + Ping903 is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + Ping903 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Ping903. If not, see <http://www.gnu.org/licenses/>. +*/ +#include <config.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <signal.h> +#include <sys/wait.h> +#include <errno.h> +#include <string.h> +#include <pthread.h> +#include "ping903.h" +#include "json.h" + +static char *config_file = "/etc/ping903.conf"; +static int verbose; + + +void +emalloc_die(void) +{ + fatal("not enough memory"); + exit(2); +} + +void * +emalloc(size_t s) +{ + void *p = malloc(s); + if (!p) + emalloc_die(); + return p; +} + +void * +e2nrealloc(void *p, size_t *pn, size_t s) +{ + extern void *json_2nrealloc(void *p, size_t *pn, size_t s); + char *n = json_2nrealloc(p, pn, s); + if (!n) + emalloc_die(); + return n; +} + +char * +estrdup(char const *s) +{ + return strcpy(emalloc(strlen(s) + 1), s); +} + +enum state { + RUNNING, /* Program is running */ + TERMINATING, /* Program is terminating */ + CHILD_TERM, /* SIGTERM has been sent to the child */ + CHILD_KILL +}; +volatile enum state state; + +static void +sigterm(int sig) +{ + if (state == RUNNING) + state = TERMINATING; +} + +static void +sigalrm(int sig) +{ + if (state == CHILD_TERM) + state = CHILD_KILL; +} + +int fatal_signals[] = { + SIGHUP, + SIGINT, + SIGQUIT, + SIGTERM, + 0 +}; + +static void +sentinel(void) +{ + pid_t pid = 0; + int i; + struct sigaction act; + pid_t child_pid = 0; + int status; + + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + act.sa_handler = sigalrm; + sigaction(SIGALRM, &act, NULL); + + for (i = 0; fatal_signals[i]; i++) + sigaddset(&act.sa_mask, fatal_signals[i]); + act.sa_handler = sigterm; + for (i = 0; fatal_signals[i]; i++) + sigaction(fatal_signals[i], &act, NULL); + + while (1) { + if (pid == 0) { + if (state != RUNNING) + break; + pid = fork(); + if (pid == -1) { + error("fork: %s", strerror(errno)); + break; + } + if (pid == 0) { + ping903(); + exit(0); + } + } + + if (child_pid > 0) { + child_pid = 0; + if (WIFEXITED(status)) { + int code = WEXITSTATUS(status); + if (code || verbose > 1) + error("child exited with status %d", + code); + } else if (WIFSIGNALED(status)) { + char const *coremsg = ""; +#ifdef WCOREDUMP + if (WCOREDUMP(status)) + coremsg = " (core dumped)"; +#endif + error("child terminated on signal %d%s", + WTERMSIG(status), coremsg); + } else if (WIFSTOPPED(status)) { + error("child stopped on signal %d", + WSTOPSIG(status)); + continue; + } else { + error("child terminated with unrecognized status %d", status); + } + /* restart the child */ + pid = 0; + continue; + } + + switch (state) { + case RUNNING: + break; + + case TERMINATING: + kill(pid, SIGTERM); + alarm(5); + state = CHILD_TERM; + break; + case CHILD_TERM: + break; + + case CHILD_KILL: + kill(pid, SIGKILL); + return; + } + + child_pid = wait(&status); + if (child_pid == -1) { + if (errno != EINTR || verbose > 1) + error("wait: %s", strerror(errno)); + } + } +} + +char *pidfile; + +void +pidfile_remove(void) +{ + if (pidfile && unlink(pidfile)) + error("cannot unlink pidfile `%s': %s", + pidfile, strerror(errno)); +} + +void +pidfile_create(void) +{ + FILE *fp; + + if (!pidfile) + return; + + fp = fopen(pidfile, "w"); + if (!fp) { + error("cannot create pidfile `%s': %s", + pidfile, strerror(errno)); + exit(1); + } + fprintf(fp, "%lu", (unsigned long) getpid()); + fclose(fp); +} + +/* Check whether pidfile exists and if so, whether its PID is still + active. Exit if it is. */ +void +pidfile_check(void) +{ + unsigned long pid; + FILE *fp; + + if (!pidfile) + return; + + fp = fopen(pidfile, "r"); + + if (fp) { + if (fscanf(fp, "%lu", &pid) != 1) { + error("cannot get pid from pidfile `%s'", pidfile); + } else { + if (kill(pid, 0) == 0) { + error("%s appears to run with pid %lu. " + "If it does not, remove `%s' and retry.", + progname, pid, pidfile); + exit(1); + } + } + + fclose(fp); + if (unlink(pidfile)) { + error("cannot unlink pidfile `%s': %s", + pidfile, strerror(errno)); + exit(1); + } + } else if (errno != ENOENT) { + error("cannot open pidfile `%s': %s", + pidfile, strerror(errno)); + exit(1); + } +} + +int +main(int argc, char **argv) +{ + int c; + int foreground = 0; + int single_process = 0; + + set_progname(argv[0]); + + while ((c = getopt(argc, argv, "c:fsv")) != EOF) { + switch (c) { + case 'c': + config_file = optarg; + break; + case 'f': + foreground = 1; + break; + case 's': + single_process = 1; + break; + case 'v': + verbose++; + break; + default: + exit(1); + } + } + + if (readconfig(config_file)) + exit(1); + + pidfile_check(); + + if (!foreground) { + if (daemon(0, 0)) { + error("daemon failed: %s", strerror(errno)); + exit(1); + } + syslog_enable(); + } + pidfile_create(); + if (single_process) + ping903(); + else + sentinel(); + + pidfile_remove(); + return 0; +} |