root/sbin/wdogctl/wdogctl.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);
}