458 lines
11 KiB
C
458 lines
11 KiB
C
/* $NetBSD: wdogctl.c,v 1.21 2015/05/06 23:08:30 pgoyette Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2000 Zembu Labs, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Author: Jason R. Thorpe <thorpej@zembu.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by Zembu Labs, Inc.
|
|
* 4. Neither the name of Zembu Labs nor the names of its employees may
|
|
* be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY ZEMBU LABS, INC. ``AS IS'' AND ANY EXPRESS
|
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WAR-
|
|
* RANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DIS-
|
|
* CLAIMED. IN NO EVENT SHALL ZEMBU LABS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include <sys/cdefs.h>
|
|
|
|
#ifndef lint
|
|
__RCSID("$NetBSD: wdogctl.c,v 1.21 2015/05/06 23:08:30 pgoyette Exp $");
|
|
#endif
|
|
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/wdog.h>
|
|
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <paths.h>
|
|
|
|
static void enable_kernel(const char *, u_int);
|
|
static void enable_user(const char *, u_int, int);
|
|
static void enable_ext(const char *, u_int);
|
|
static void tickle_ext(void);
|
|
static void disable(void);
|
|
static void prep_wmode(struct wdog_mode *, int, const char *, u_int);
|
|
static void list_timers(void);
|
|
__dead static void usage(void);
|
|
|
|
static int Aflag;
|
|
|
|
/* Caution -- ordered list; entries >= CMD_EXT_TICKLE set timers */
|
|
enum cmd {
|
|
CMD_NONE, /* No verb given */
|
|
CMD_DISABLE,
|
|
CMD_DOTICKLE,
|
|
CMD_EXT_TICKLE,
|
|
CMD_KERN_TICKLE,
|
|
CMD_NOCANCEL_TICKLE,
|
|
CMD_USER_TICKLE
|
|
};
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
enum cmd command = CMD_NONE;
|
|
int period_flag = 0;
|
|
int ch, tmp;
|
|
u_int period = WDOG_PERIOD_DEFAULT;
|
|
|
|
while ((ch = getopt(argc, argv, "Adekp:utx")) != -1) {
|
|
switch (ch) {
|
|
case 'A':
|
|
Aflag = 1;
|
|
break;
|
|
|
|
case 'd':
|
|
if (command != CMD_NONE)
|
|
usage();
|
|
command = CMD_DISABLE;
|
|
break;
|
|
|
|
case 'e':
|
|
if (command != CMD_NONE)
|
|
usage();
|
|
command = CMD_EXT_TICKLE;
|
|
break;
|
|
|
|
case 'k':
|
|
if (command != CMD_NONE)
|
|
usage();
|
|
command = CMD_KERN_TICKLE;
|
|
break;
|
|
|
|
case 't':
|
|
if (command != CMD_NONE)
|
|
usage();
|
|
command = CMD_DOTICKLE;
|
|
break;
|
|
|
|
case 'p':
|
|
period_flag = 1;
|
|
tmp = atoi(optarg);
|
|
if (tmp < 0)
|
|
usage();
|
|
period = (unsigned int)tmp;
|
|
break;
|
|
|
|
case 'x':
|
|
case 'u':
|
|
if (command != CMD_NONE)
|
|
usage();
|
|
command =
|
|
(ch == 'u') ? CMD_USER_TICKLE : CMD_NOCANCEL_TICKLE;
|
|
break;
|
|
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (command < CMD_EXT_TICKLE) {
|
|
if (Aflag || period_flag)
|
|
usage();
|
|
if (argc != 0)
|
|
usage();
|
|
} else if (argc != 1)
|
|
usage();
|
|
|
|
switch (command) {
|
|
case CMD_NONE:
|
|
list_timers();
|
|
break;
|
|
case CMD_DISABLE:
|
|
disable();
|
|
break;
|
|
case CMD_DOTICKLE:
|
|
tickle_ext();
|
|
break;
|
|
case CMD_EXT_TICKLE:
|
|
enable_ext(argv[0], period);
|
|
break;
|
|
case CMD_KERN_TICKLE:
|
|
enable_kernel(argv[0], period);
|
|
break;
|
|
case CMD_NOCANCEL_TICKLE:
|
|
case CMD_USER_TICKLE:
|
|
enable_user(argv[0], period, command == CMD_USER_TICKLE);
|
|
break;
|
|
}
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
prep_wmode(struct wdog_mode *wp, int mode, const char *name, u_int period)
|
|
{
|
|
if (strlen(name) >= WDOG_NAMESIZE)
|
|
errx(EXIT_FAILURE, "invalid watchdog timer name: %s", name);
|
|
|
|
strlcpy(wp->wm_name, name, sizeof(wp->wm_name));
|
|
wp->wm_mode = mode;
|
|
wp->wm_period = period;
|
|
if (Aflag)
|
|
wp->wm_mode |= WDOG_FEATURE_ALARM;
|
|
}
|
|
|
|
static void
|
|
enable_kernel(const char *name, u_int period)
|
|
{
|
|
struct wdog_mode wm;
|
|
int fd;
|
|
|
|
prep_wmode(&wm, WDOG_MODE_KTICKLE, name, period);
|
|
|
|
fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
|
|
if (fd == -1)
|
|
err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
|
|
|
|
if (ioctl(fd, WDOGIOC_SMODE, &wm) == -1)
|
|
err(EXIT_FAILURE, "WDOGIOC_SMODE");
|
|
|
|
(void)close(fd);
|
|
}
|
|
|
|
static void
|
|
enable_ext(const char *name, u_int period)
|
|
{
|
|
struct wdog_mode wm;
|
|
int fd;
|
|
|
|
prep_wmode(&wm, WDOG_MODE_ETICKLE, name, period);
|
|
|
|
fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
|
|
if (fd == -1)
|
|
err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
|
|
if (ioctl(fd, WDOGIOC_SMODE, &wm) == -1) {
|
|
err(EXIT_FAILURE, "WDOGIOC_SMODE");
|
|
}
|
|
if (ioctl(fd, WDOGIOC_TICKLE) == -1)
|
|
syslog(LOG_EMERG, "unable to tickle watchdog timer %s: %m",
|
|
wm.wm_name);
|
|
|
|
(void)close(fd);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
enable_user(const char *name, u_int period, int cancel_on_close)
|
|
{
|
|
struct wdog_mode wm;
|
|
struct timespec ts;
|
|
pid_t tickler;
|
|
int fd, rv;
|
|
|
|
prep_wmode(&wm,
|
|
(cancel_on_close) ? WDOG_MODE_UTICKLE : WDOG_MODE_ETICKLE, name,
|
|
period);
|
|
|
|
fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
|
|
if (fd == -1)
|
|
err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
|
|
|
|
/* ...so we can log failures to tickle the timer. */
|
|
openlog("wdogctl", LOG_PERROR|LOG_PID, LOG_DAEMON);
|
|
|
|
/*
|
|
* We fork a child process which detaches from the controlling
|
|
* terminal once the timer is armed, and tickles the timer
|
|
* until we send it a SIGTERM.
|
|
*/
|
|
tickler = fork();
|
|
if (tickler == -1)
|
|
err(EXIT_FAILURE, "unable to fork tickler process");
|
|
else if (tickler != 0) {
|
|
if (ioctl(fd, WDOGIOC_SMODE, &wm) == -1) {
|
|
(void)kill(tickler, SIGTERM);
|
|
err(EXIT_FAILURE, "WDOGIOC_SMODE");
|
|
}
|
|
(void)close(fd);
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Wait for the watchdog to be armed. When it is, loop,
|
|
* tickling the timer, then waiting 1/2 the period before
|
|
* doing it again.
|
|
*
|
|
* If the parent fails to enable the watchdog, it will kill
|
|
* us.
|
|
*/
|
|
do {
|
|
rv = ioctl(fd, WDOGIOC_WHICH, &wm);
|
|
} while (rv == -1);
|
|
|
|
if (ioctl(fd, WDOGIOC_TICKLE) == -1)
|
|
syslog(LOG_EMERG, "unable to tickle watchdog timer %s: %m",
|
|
wm.wm_name);
|
|
|
|
/*
|
|
* Now detach from the controlling terminal, and just run
|
|
* in the background. The kernel will keep track of who
|
|
* we are, each time we tickle the timer.
|
|
*/
|
|
if (daemon(0, 0) == -1) {
|
|
/*
|
|
* We weren't able to go into the background. When
|
|
* we exit, the kernel will disable the watchdog so
|
|
* that the system won't die.
|
|
*/
|
|
err(EXIT_FAILURE, "unable to detach from terminal");
|
|
}
|
|
|
|
if (ioctl(fd, WDOGIOC_TICKLE) == -1)
|
|
syslog(LOG_EMERG, "unable to tickle watchdog timer %s: %m",
|
|
wm.wm_name);
|
|
|
|
for (;;) {
|
|
ts.tv_sec = wm.wm_period / 2;
|
|
ts.tv_nsec = 0;
|
|
(void)nanosleep(&ts, NULL);
|
|
|
|
if (ioctl(fd, WDOGIOC_TICKLE) == -1)
|
|
syslog(LOG_EMERG,
|
|
"unable to tickle watchdog timer %s: %m",
|
|
wm.wm_name);
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
static void
|
|
tickle_ext(void)
|
|
{
|
|
int fd;
|
|
|
|
fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
|
|
if (fd == -1)
|
|
err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
|
|
if (ioctl(fd, WDOGIOC_TICKLE) == -1)
|
|
fprintf(stderr, "Cannot tickle timer\n");
|
|
|
|
(void)close(fd);
|
|
}
|
|
|
|
static void
|
|
disable(void)
|
|
{
|
|
struct wdog_mode wm;
|
|
pid_t tickler;
|
|
int fd, mode;
|
|
|
|
fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
|
|
if (fd == -1)
|
|
err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
|
|
|
|
if (ioctl(fd, WDOGIOC_WHICH, &wm) == -1) {
|
|
printf("No watchdog timer running.\n");
|
|
(void)close(fd);
|
|
return;
|
|
}
|
|
mode = wm.wm_mode & WDOG_MODE_MASK;
|
|
|
|
/*
|
|
* If the timer is running in UTICKLE mode, we need
|
|
* to kill the wdogctl(8) process that is tickling
|
|
* the timer.
|
|
*/
|
|
if (mode == WDOG_MODE_UTICKLE) {
|
|
if (ioctl(fd, WDOGIOC_GTICKLER, &tickler) == -1)
|
|
err(EXIT_FAILURE, "WDOGIOC_GTICKLER");
|
|
(void)close(fd);
|
|
(void)kill(tickler, SIGTERM);
|
|
} else {
|
|
wm.wm_mode = WDOG_MODE_DISARMED;
|
|
if (ioctl(fd, WDOGIOC_SMODE, &wm) == -1) {
|
|
err(EXIT_FAILURE, "unable to disarm watchdog %s",
|
|
wm.wm_name);
|
|
}
|
|
(void)close(fd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
list_timers(void)
|
|
{
|
|
struct wdog_conf wc;
|
|
struct wdog_mode wm;
|
|
char *buf, *cp;
|
|
int fd, count, i, mode;
|
|
pid_t tickler;
|
|
|
|
fd = open(_PATH_WATCHDOG, O_RDONLY, 0644);
|
|
if (fd == -1)
|
|
err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
|
|
|
|
wc.wc_names = NULL;
|
|
wc.wc_count = 0;
|
|
|
|
if (ioctl(fd, WDOGIOC_GWDOGS, &wc) == -1)
|
|
err(EXIT_FAILURE, "ioctl WDOGIOC_GWDOGS for count");
|
|
|
|
count = wc.wc_count;
|
|
if (count == 0) {
|
|
printf("No watchdog timers present.\n");
|
|
goto out;
|
|
}
|
|
|
|
buf = malloc(count * WDOG_NAMESIZE);
|
|
if (buf == NULL)
|
|
err(EXIT_FAILURE, "malloc %d byte for watchdog names",
|
|
count * WDOG_NAMESIZE);
|
|
|
|
wc.wc_names = buf;
|
|
if (ioctl(fd, WDOGIOC_GWDOGS, &wc) == -1)
|
|
err(EXIT_FAILURE, "ioctl WDOGIOC_GWDOGS for names");
|
|
|
|
count = wc.wc_count;
|
|
if (count == 0) {
|
|
printf("No watchdog timers present.\n");
|
|
free(buf);
|
|
goto out;
|
|
}
|
|
|
|
printf("Available watchdog timers:\n");
|
|
for (i = 0, cp = buf; i < count; i++, cp += WDOG_NAMESIZE) {
|
|
cp[WDOG_NAMESIZE - 1] = '\0';
|
|
strlcpy(wm.wm_name, cp, sizeof(wm.wm_name));
|
|
|
|
if (ioctl(fd, WDOGIOC_GMODE, &wm) == -1)
|
|
continue;
|
|
mode = wm.wm_mode & WDOG_MODE_MASK;
|
|
if (mode == WDOG_MODE_UTICKLE) {
|
|
if (ioctl(fd, WDOGIOC_GTICKLER, &tickler) == -1)
|
|
tickler = (pid_t) -1;
|
|
}
|
|
|
|
printf("\t%s, %u second period", cp, wm.wm_period);
|
|
if (mode != WDOG_MODE_DISARMED) {
|
|
switch(mode) {
|
|
case WDOG_MODE_KTICKLE:
|
|
printf(" [armed, kernel tickle");
|
|
break;
|
|
case WDOG_MODE_UTICKLE:
|
|
printf(" [armed, user tickle");
|
|
if (tickler != (pid_t) -1)
|
|
printf(", pid %d", tickler);
|
|
break;
|
|
case WDOG_MODE_ETICKLE:
|
|
printf(" [armed, external tickle");
|
|
break;
|
|
}
|
|
printf("]");
|
|
}
|
|
printf("\n");
|
|
}
|
|
out:
|
|
(void)close(fd);
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
|
|
fprintf(stderr, "usage: %s\n", getprogname());
|
|
fprintf(stderr, " %s -d\n", getprogname());
|
|
fprintf(stderr, " %s -e [-A] [-p seconds] timer\n",
|
|
getprogname());
|
|
fprintf(stderr, " %s -k [-A] [-p seconds] timer\n",
|
|
getprogname());
|
|
fprintf(stderr, " %s -t\n", getprogname());
|
|
fprintf(stderr, " %s -u [-A] [-p seconds] timer\n",
|
|
getprogname());
|
|
fprintf(stderr, " %s -x [-A] [-p seconds] timer\n",
|
|
getprogname());
|
|
|
|
exit(1);
|
|
}
|