blob: daeb2aefdfa17528a5e0467f7e87d23abfd04fbe [file] [log] [blame]
/**********************************************************
* Copyright: Appliance Studio Ltd
* License: GPL
*
* Filename: $Id: wd_keepalive.c,v 1.6 2007/08/17 09:24:54 meskes Exp $
* Author: Marcel Jansen, 22 February 2001
* Michael Meskes, since then
* Purpose: This program can be run during critical periods
* when the normal watcdog shouldn't be run. It will
* read from the same configuration file, it will do
* no checks but will keep writing to the device
*
***********************************************************/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <linux/watchdog.h>
#include <libgen.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#if USE_SYSLOG
#include <syslog.h>
#endif
#define TRUE 1
#define FALSE 0
#define DEVICE "watchdog-device"
#define INTERVAL "interval"
#define PRIORITY "priority"
#define REALTIME "realtime"
int watchdog = -1, tint = 10, schedprio = 1;
char *devname = NULL, *progname = NULL;
volatile sig_atomic_t _running = 1;
#if defined(_POSIX_MEMLOCK)
int mlocked = FALSE, realtime = FALSE;
#endif
static void usage(void)
{
fprintf(stderr, "%s version %d.%d, usage:\n", progname, MAJOR_VERSION, MINOR_VERSION);
fprintf(stderr, "%s \n", progname);
exit(1);
}
/* write a log entry on exit */
static void log_end()
{
#if USE_SYSLOG
/* Log the closing message */
syslog(LOG_INFO, "stopping watchdog keepalive daemon (%d.%d)", MAJOR_VERSION, MINOR_VERSION);
closelog();
sleep(5); /* make sure log is written */
#endif /* USE_SYSLOG */
return;
}
/* close the device and check for error */
static void close_all()
{
if (watchdog != -1) {
if (write(watchdog, "V", 1) < 0 ) {
int err = errno;
#if USE_SYSLOG
syslog(LOG_ERR, "write watchdog device gave error %d = '%m'!", err);
#else /* USE_SYSLOG */
perror(progname);
#endif /* USE_SYSLOG */
}
if (close(watchdog) == -1) {
#if USE_SYSLOG
syslog(LOG_ALERT, "cannot close %s (errno = %d)", devname, errno);
#else /* USE_SYSLOG */
perror(progname);
#endif /* USE_SYSLOG */
}
}
}
void sigterm_handler(int arg) {
_running = 0;
}
/* on exit we close the device and log that we stop */
void terminate(void) {
#if defined(_POSIX_MEMLOCK)
if ( realtime == TRUE && mlocked == TRUE ) {
/* unlock all locked pages */
if ( munlockall() != 0 ) {
#if USE_SYSLOG
syslog(LOG_ERR, "cannot unlock realtime memory (errno = %d)", errno);
#else /* USE_SYSLOG */
perror(progname);
#endif /* USE_SYSLOG */
}
}
#endif /* _POSIX_MEMLOCK */
close_all();
log_end();
exit(0);
}
static int spool(char *line, int *i, int offset)
{
for ( (*i) += offset; line[*i] == ' ' || line[*i] == '\t'; (*i)++ );
if ( line[*i] == '=' )
(*i)++;
for ( ; line[*i] == ' ' || line[*i] == '\t'; (*i)++ );
if ( line[*i] == '\0' )
return(1);
else
return(0);
}
static void read_config(char *configfile, char *progname)
{
FILE *wc;
if ( (wc = fopen(configfile, "r")) == NULL ) {
perror(progname);
exit(1);
}
while ( !feof(wc) ) {
char *line = NULL;
size_t n;
if (getline(&line, &n, wc) == -1) {
if ( !ferror(wc) )
break;
else {
perror(progname);
exit(1);
}
}
else {
int i, j;
/* scan the actual line for an option */
/* first remove the leading blanks */
for ( i = 0; line[i] == ' ' || line[i] == '\t'; i++ );
/* if the next sign is a '#' we have a comment */
if ( line[i] == '#' )
continue;
/* also remove the trailing blanks and the \n */
for ( j = strlen(line) - 1; line[j] == ' ' || line[j] == '\t' || line[j] == '\n'; j-- );
line[j + 1] = '\0';
/* if the line is empty now, we don't have to parse it */
if ( strlen(line + i) == 0 )
continue;
/* now check for an option */
if ( strncmp(line + i, INTERVAL, strlen(INTERVAL)) == 0 ) {
if ( spool(line, &i, strlen(INTERVAL)) )
fprintf(stderr, "Ignoring invalid line in config file:\n%s\n", line);
else
tint = atol(line + i);
}
else if ( strncmp(line + i, DEVICE, strlen(DEVICE)) == 0 ) {
if ( spool(line, &i, strlen(DEVICE)) )
devname = NULL;
else
devname = strdup(line + i);
} else if (strncmp(line + i, REALTIME, strlen(REALTIME)) == 0) {
(void)spool(line, &i, strlen(REALTIME));
realtime = (strncmp(line + i, "yes", 3) == 0) ? TRUE : FALSE;
} else if (strncmp(line + i, PRIORITY, strlen(PRIORITY)) == 0) {
if (spool(line, &i, strlen(PRIORITY)))
fprintf(stderr, "Ignoring invalid line in config file:\n%s\n", line);
else
schedprio = atol(line + i);
}
else {
/*
* do not print an error message here because we usually use
* watchdog's config file which may contain far more valid
* options than we understand
*/
/* fprintf(stderr, "Ignoring config line: %s\n", line); */
}
}
}
if ( fclose(wc) != 0 ) {
perror(progname);
exit(1);
}
}
int main(int argc, char *const argv[])
{
FILE *fp;
char *configfile = CONFIG_FILENAME;
pid_t child_pid;
int count = 0;
int c;
int oom_adjusted = 0;
struct stat s;
/* allow all options watchdog understands too */
#if USE_SYSLOG
char *opts = "d:i:n:fsvbql:p:t:c:r:m:a:";
struct option long_options[] =
{
{"config-file", required_argument, NULL, 'c'},
{"force", no_argument, NULL, 'f'},
{"sync", no_argument, NULL, 's'},
{"no-action", no_argument, NULL, 'q'},
{"verbose", no_argument, NULL, 'v'},
{"softboot", no_argument, NULL, 'b'},
{NULL, 0, NULL, 0}
};
struct watchdog_info ident;
#else /* USE_SYSLOG */
char *opts = "d:i:n:fsbql:p:t:c:r:m:a:";
struct option long_options[] =
{
{"config-file", required_argument, NULL, 'c'},
{"force", no_argument, NULL, 'f'},
{"sync", no_argument, NULL, 's'},
{"no-action", no_argument, NULL, 'q'},
{"softboot", no_argument, NULL, 'b'},
{NULL, 0, NULL, 0}
};
#endif /* USE_SYSLOG */
progname = basename(argv[0]);
/* check for the one option we understand */
while ((c = getopt_long(argc, argv, opts, long_options, NULL)) != EOF) {
if (c == -1)
break;
switch (c) {
case 'c':
configfile = optarg;
break;
case 'n':
case 'p':
case 'a':
case 'r':
case 'd':
case 't':
case 'l':
case 'm':
case 'i':
case 'f':
case 's':
case 'b':
case 'q':
#if USE_SYSLOG
case 'v':
#endif /* USE_SYSLOG */
break;
default:
usage();
}
}
read_config(configfile, progname);
/* make sure we're on the root partition */
if ( chdir("/") < 0 ) {
perror(progname);
exit(1);
}
#if !defined(DEBUG)
/* fork to go into the background */
if ( (child_pid = fork()) < 0 ) {
perror(progname);
exit(1);
}
else if ( child_pid > 0 ) {
/* fork was okay */
/* wait for child to exit */
if ( waitpid(child_pid, NULL, 0) != child_pid ) {
perror(progname);
exit(1);
}
/* and exit myself */
exit(0);
}
/* and fork again to make sure we inherit all rights from init */
if ( (child_pid = fork()) < 0 ) {
perror(progname);
exit(1);
}
else if ( child_pid > 0 )
exit(0);
#endif /* !DEBUG */
/* now we're free */
#if USE_SYSLOG
#if !defined(DEBUG)
/* Okay, we're a daemon */
/* but we're still attached to the tty */
/* create our own session */
setsid();
/* with USE_SYSLOG we don't do any console IO */
close(0);
close(1);
close(2);
#endif /* !DEBUG */
/* Log the starting message */
openlog(progname, LOG_PID, LOG_DAEMON);
syslog(LOG_INFO, "starting watchdog keepalive daemon (%d.%d):", MAJOR_VERSION, MINOR_VERSION);
if (devname == NULL)
syslog(LOG_INFO, " no watchdog device configured, aborting");
else
syslog(LOG_INFO, " int=%d alive=%s realtime=%s", tint, devname, realtime ? "yes" : "no");
#endif /* USE_SYSLOG */
/* this daemon has no other function than writing to this device
* i.e. if there is no device given we better punt */
if ( devname == NULL )
terminate();
/* open the device */
watchdog = open(devname, O_WRONLY);
if ( watchdog == -1 ) {
#if USE_SYSLOG
syslog(LOG_ERR, "cannot open %s (errno = %d = '%m')", devname, errno);
#else /* USE_SYSLOG */
perror(progname);
#endif /* USE_SYSLOG */
exit(1);
}
#if USE_SYSLOG
/* Also log watchdog identity */
if (ioctl(watchdog, WDIOC_GETSUPPORT, &ident) < 0) {
syslog(LOG_ERR, "cannot get watchdog identity (errno = %d = '%m')", errno);
}
else {
ident.identity[sizeof(ident.identity) - 1] = '\0'; /* Be sure */
syslog(LOG_INFO, "hardware wartchdog identity: %s", ident.identity);
}
#endif
/* tuck my process id away */
fp = fopen(KA_PIDFILE, "w");
if ( fp != NULL ) {
fprintf(fp, "%d\n", getpid());
(void) fclose(fp);
}
/* set signal term to call sigterm_handler() */
/* to make sure watchdog device is closed */
signal(SIGTERM, sigterm_handler);
#if defined(_POSIX_MEMLOCK)
if ( realtime == TRUE ) {
/* lock all actual and future pages into memory */
if ( mlockall(MCL_CURRENT | MCL_FUTURE) != 0 ) {
#if USE_SYSLOG
syslog(LOG_ERR, "cannot lock realtime memory (errno = %d = '%m')", errno);
#else /* USE_SYSLOG */
perror(progname);
#endif /* USE_SYSLOG */
}
else {
struct sched_param sp;
/* now set the scheduler */
sp.sched_priority = schedprio;
if ( sched_setscheduler(0, SCHED_RR, &sp) != 0 ) {
#if USE_SYSLOG
syslog(LOG_ERR, "cannot set scheduler (errno = %d = '%m')", errno);
#else /* USE_SYSLOG */
perror(progname);
#endif /* USE_SYSLOG */
}
else
mlocked = TRUE;
}
}
#endif
/* tell oom killer to not kill this process */
#ifdef OOM_SCORE_ADJ_MIN
if ( ! stat("/proc/self/oom_score_adj", &s) ) {
fp = fopen("/proc/self/oom_score_adj", "w");
if (fp) {
fprintf(fp, "%d\n", OOM_SCORE_ADJ_MIN);
(void) fclose(fp);
oom_adjusted = 1;
}
}
#endif
#ifdef OOM_DISABLE
if ( ! oom_adjusted ) {
if ( ! stat("/proc/self/oom_adj", &s) ) {
fp = fopen("/proc/self/oom_adj", "w");
if (fp) {
fprintf(fp, "%d\n", OOM_DISABLE);
(void) fclose(fp);
oom_adjusted = 1;
}
}
}
#endif
#if USE_SYSLOG
if ( ! oom_adjusted ) {
syslog(LOG_WARNING, "unable to disable oom handling!");
}
#endif /* USE_SYSLOG */
/* main loop: update after <tint> seconds */
while ( _running ) {
if ( write(watchdog, "\0", 1) < 0 ) {
int err = errno;
#if USE_SYSLOG
syslog(LOG_ERR, "write watchdog device gave error %d = '%m'!", err);
#else /* USE_SYSLOG */
perror(progname);
#endif /* USE_SYSLOG */
}
/* finally sleep some seconds */
sleep(tint);
count++;
}
terminate();
/* not reached */
return 0;
}