/* This file is part of Ping903
Copyright (C) 2020-2023 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ping903.h"
#include "json.h"
#include "defs.h"
int verbose;
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);
}
}
void
usage(void)
{
printf("Usage: %s [-fhsVv] [-c FILE]\n", progname);
printf("High-performance ICMP monitoring daemon.\n");
printf("\n");
printf("Options:\n\n");
printf(" -c FILE read configuration from FILE\n");
printf(" -f remain in foreground\n");
printf(" -h print this help test\n");
printf(" -s don't start supervisor process\n");
printf(" -V print program version and exit\n");
printf(" -v additional verbosity\n");
printf("\n");
printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
printf("%s home page: <%s>\n", PACKAGE_NAME, PACKAGE_URL);
}
void
version(void)
{
printf("%s\n", PACKAGE_STRING);
printf("%s", COPYLEFT);
}
int
main(int argc, char **argv)
{
int c;
char *config_file = NULL;
int foreground = 0;
int single_process = 0;
set_progname(argv[0]);
if (argc == 2) {
if (strcmp(argv[1], "--help") == 0) {
usage();
exit(0);
}
if (strcmp(argv[1], "--version") == 0) {
version();
exit(0);
}
}
while ((c = getopt(argc, argv, "c:fhsVv")) != EOF) {
switch (c) {
case 'c':
config_file = optarg;
break;
case 'f':
foreground = 1;
break;
case 'h':
usage();
exit(0);
case 's':
single_process = 1;
break;
case 'v':
verbose++;
break;
case 'V':
version();
exit(0);
default:
exit(EX_USAGE);
}
}
pinger_setup();
if (!config_file) {
config_file = DEFAULT_CONFIG_FILE;
if (access(config_file, F_OK) == 0) {
if (readconfig(config_file))
exit(EX_CONFIG);
} else
info("configuration file %s does not exist; "
"assuming defaults",
config_file);
} else if (readconfig(config_file))
exit(EX_CONFIG);
pidfile_check();
if (!foreground) {
if (daemon(0, 0)) {
error("daemon failed: %s", strerror(errno));
exit(EX_OSERR);
}
syslog_enable();
}
pidfile_create();
if (single_process)
ping903();
else
sentinel();
pidfile_remove();
return 0;
}