1/*	$NetBSD: wdogctl.c,v 1.20 2011/08/27 19:00:35 joerg Exp $	*/
2
3/*-
4 * Copyright (c) 2000 Zembu Labs, Inc.
5 * All rights reserved.
6 *
7 * Author: Jason R. Thorpe <thorpej@zembu.com>
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 *    must display the following acknowledgement:
19 *	This product includes software developed by Zembu Labs, Inc.
20 * 4. Neither the name of Zembu Labs nor the names of its employees may
21 *    be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY ZEMBU LABS, INC. ``AS IS'' AND ANY EXPRESS
25 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WAR-
26 * RANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DIS-
27 * CLAIMED.  IN NO EVENT SHALL ZEMBU LABS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35#include <sys/cdefs.h>
36
37#ifndef lint
38__RCSID("$NetBSD: wdogctl.c,v 1.20 2011/08/27 19:00:35 joerg Exp $");
39#endif
40
41
42#include <sys/param.h>
43#include <sys/ioctl.h>
44#include <sys/wdog.h>
45
46#include <err.h>
47#include <errno.h>
48#include <fcntl.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <time.h>
52#include <signal.h>
53#include <syslog.h>
54#include <unistd.h>
55#include <string.h>
56#include <paths.h>
57
58static void	enable_kernel(const char *, u_int);
59static void	enable_user(const char *, u_int, int);
60static void	enable_ext(const char *, u_int);
61static void	tickle_ext(void);
62static void	disable(void);
63static void	prep_wmode(struct wdog_mode *, int,  const char *, u_int);
64static void	list_timers(void);
65__dead static void	usage(void);
66
67static int	Aflag;
68
69/* Caution -- ordered list; entries >= CMD_EXT_TICKLE set timers */
70enum	cmd {
71	CMD_NONE,	/* No verb given */
72	CMD_DISABLE,
73	CMD_DOTICKLE,
74	CMD_EXT_TICKLE,
75	CMD_KERN_TICKLE,
76	CMD_NOCANCEL_TICKLE,
77	CMD_USER_TICKLE
78};
79
80int
81main(int argc, char *argv[])
82{
83	enum cmd command = CMD_NONE;
84	int period_flag = 0;
85	int ch, tmp;
86	u_int period = WDOG_PERIOD_DEFAULT;
87
88	while ((ch = getopt(argc, argv, "Adekp:utx")) != -1) {
89		switch (ch) {
90		case 'A':
91			Aflag = 1;
92			break;
93
94		case 'd':
95			if (command != CMD_NONE)
96				usage();
97			command = CMD_DISABLE;
98			break;
99
100		case 'e':
101			if (command != CMD_NONE)
102				usage();
103			command = CMD_EXT_TICKLE;
104			break;
105
106		case 'k':
107			if (command != CMD_NONE)
108				usage();
109			command = CMD_KERN_TICKLE;
110			break;
111
112		case 't':
113			if (command != CMD_NONE)
114				usage();
115			command = CMD_DOTICKLE;
116			break;
117
118		case 'p':
119			period_flag = 1;
120			tmp = atoi(optarg);
121			if (tmp < 0)
122				usage();
123			period = (unsigned int)tmp;
124			break;
125
126		case 'x':
127		case 'u':
128			if (command != CMD_NONE)
129				usage();
130			command =
131			    (ch == 'u') ? CMD_USER_TICKLE : CMD_NOCANCEL_TICKLE;
132			break;
133
134		default:
135			usage();
136		}
137	}
138
139	argc -= optind;
140	argv += optind;
141
142	if (command < CMD_EXT_TICKLE) {
143		if (Aflag || period_flag)
144			usage();
145		if (argc != 0)
146			usage();
147	} else if (argc != 1)
148		usage();
149
150	switch (command) {
151	case CMD_NONE:
152		list_timers();
153		break;
154	case CMD_DISABLE:
155		disable();
156		break;
157	case CMD_DOTICKLE:
158		tickle_ext();
159		break;
160	case CMD_EXT_TICKLE:
161		enable_ext(argv[0], period);
162		break;
163	case CMD_KERN_TICKLE:
164		enable_kernel(argv[0], period);
165		break;
166	case CMD_NOCANCEL_TICKLE:
167	case CMD_USER_TICKLE:
168		enable_user(argv[0], period, command == CMD_USER_TICKLE);
169		break;
170	}
171	exit(EXIT_SUCCESS);
172}
173
174static void
175prep_wmode(struct wdog_mode *wp, int mode,  const char *name, u_int period)
176{
177	if (strlen(name) >= WDOG_NAMESIZE)
178		errx(EXIT_FAILURE, "invalid watchdog timer name: %s", name);
179
180	strlcpy(wp->wm_name, name, sizeof(wp->wm_name));
181	wp->wm_mode = mode;
182	wp->wm_period = period;
183	if (Aflag)
184		wp->wm_mode |= WDOG_FEATURE_ALARM;
185}
186
187static void
188enable_kernel(const char *name, u_int period)
189{
190	struct wdog_mode wm;
191	int fd;
192
193	prep_wmode(&wm, WDOG_MODE_KTICKLE, name, period);
194
195	fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
196	if (fd == -1)
197		err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
198
199	if (ioctl(fd, WDOGIOC_SMODE, &wm) == -1)
200		err(EXIT_FAILURE, "WDOGIOC_SMODE");
201
202	(void)close(fd);
203}
204
205static void
206enable_ext(const char *name, u_int period)
207{
208	struct wdog_mode wm;
209	int fd;
210
211	prep_wmode(&wm, WDOG_MODE_ETICKLE, name, period);
212
213	fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
214	if (fd == -1)
215		err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
216	if (ioctl(fd, WDOGIOC_SMODE, &wm) == -1) {
217		err(EXIT_FAILURE, "WDOGIOC_SMODE");
218	}
219	if (ioctl(fd, WDOGIOC_TICKLE) == -1)
220		syslog(LOG_EMERG, "unable to tickle watchdog timer %s: %m",
221		    wm.wm_name);
222
223	(void)close(fd);
224	return;
225}
226
227static void
228enable_user(const char *name, u_int period, int cancel_on_close)
229{
230	struct wdog_mode wm;
231	struct timespec ts;
232	pid_t tickler;
233	int fd, rv;
234
235	prep_wmode(&wm,
236	    (cancel_on_close) ? WDOG_MODE_UTICKLE : WDOG_MODE_ETICKLE, name,
237	    period);
238
239	fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
240	if (fd == -1)
241		err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
242
243	/* ...so we can log failures to tickle the timer. */
244	openlog("wdogctl", LOG_PERROR|LOG_PID, LOG_DAEMON);
245
246	/*
247	 * We fork a child process which detaches from the controlling
248	 * terminal once the timer is armed, and tickles the timer
249	 * until we send it a SIGTERM.
250	 */
251	tickler = fork();
252	if (tickler == -1)
253		err(EXIT_FAILURE, "unable to fork tickler process");
254	else if (tickler != 0) {
255		if (ioctl(fd, WDOGIOC_SMODE, &wm) == -1) {
256			(void)kill(tickler, SIGTERM);
257			err(EXIT_FAILURE, "WDOGIOC_SMODE");
258		}
259		(void)close(fd);
260		return;
261	}
262
263
264	/*
265	 * Wait for the watchdog to be armed.  When it is, loop,
266	 * tickling the timer, then waiting 1/2 the period before
267	 * doing it again.
268	 *
269	 * If the parent fails to enable the watchdog, it will kill
270	 * us.
271	 */
272	do {
273		rv = ioctl(fd, WDOGIOC_WHICH, &wm);
274	} while (rv == -1);
275
276	if (ioctl(fd, WDOGIOC_TICKLE) == -1)
277		syslog(LOG_EMERG, "unable to tickle watchdog timer %s: %m",
278		    wm.wm_name);
279
280	/*
281	 * Now detach from the controlling terminal, and just run
282	 * in the background.  The kernel will keep track of who
283	 * we are, each time we tickle the timer.
284	 */
285	if (daemon(0, 0) == -1) {
286		/*
287		 * We weren't able to go into the background.  When
288		 * we exit, the kernel will disable the watchdog so
289		 * that the system won't die.
290		 */
291		err(EXIT_FAILURE, "unable to detach from terminal");
292	}
293
294	if (ioctl(fd, WDOGIOC_TICKLE) == -1)
295		syslog(LOG_EMERG, "unable to tickle watchdog timer %s: %m",
296		    wm.wm_name);
297
298	for (;;) {
299		ts.tv_sec = wm.wm_period / 2;
300		ts.tv_nsec = 0;
301		(void)nanosleep(&ts, NULL);
302
303		if (ioctl(fd, WDOGIOC_TICKLE) == -1)
304			syslog(LOG_EMERG,
305			    "unable to tickle watchdog timer %s: %m",
306			    wm.wm_name);
307	}
308	/* NOTREACHED */
309}
310
311static void
312tickle_ext(void)
313{
314	int fd;
315
316	fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
317	if (fd == -1)
318		err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
319	if (ioctl(fd, WDOGIOC_TICKLE) == -1)
320		fprintf(stderr, "Cannot tickle timer\n");
321
322	(void)close(fd);
323}
324
325static void
326disable(void)
327{
328	struct wdog_mode wm;
329	pid_t tickler;
330	int fd, mode;
331
332	fd = open(_PATH_WATCHDOG, O_RDWR, 0644);
333	if (fd == -1)
334		err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
335
336	if (ioctl(fd, WDOGIOC_WHICH, &wm) == -1) {
337		printf("No watchdog timer running.\n");
338		(void)close(fd);
339		return;
340	}
341	mode = wm.wm_mode & WDOG_MODE_MASK;
342
343	/*
344	 * If the timer is running in UTICKLE mode, we need
345	 * to kill the wdogctl(8) process that is tickling
346	 * the timer.
347	 */
348	if (mode == WDOG_MODE_UTICKLE) {
349		if (ioctl(fd, WDOGIOC_GTICKLER, &tickler) == -1)
350			err(EXIT_FAILURE, "WDOGIOC_GTICKLER");
351		(void)close(fd);
352		(void)kill(tickler, SIGTERM);
353	} else {
354		wm.wm_mode = WDOG_MODE_DISARMED;
355		if (ioctl(fd, WDOGIOC_SMODE, &wm) == -1) {
356			err(EXIT_FAILURE, "unable to disarm watchdog %s",
357			    wm.wm_name);
358		}
359		(void)close(fd);
360	}
361}
362
363static void
364list_timers(void)
365{
366	struct wdog_conf wc;
367	struct wdog_mode wm;
368	char *buf, *cp;
369	int fd, count, i, mode;
370	pid_t tickler;
371
372	fd = open(_PATH_WATCHDOG, O_RDONLY, 0644);
373	if (fd == -1)
374		err(EXIT_FAILURE, "open %s", _PATH_WATCHDOG);
375
376	wc.wc_names = NULL;
377	wc.wc_count = 0;
378
379	if (ioctl(fd, WDOGIOC_GWDOGS, &wc) == -1)
380		err(EXIT_FAILURE, "ioctl WDOGIOC_GWDOGS for count");
381
382	count = wc.wc_count;
383	if (count == 0) {
384		printf("No watchdog timers present.\n");
385		goto out;
386	}
387
388	buf = malloc(count * WDOG_NAMESIZE);
389	if (buf == NULL)
390		err(EXIT_FAILURE, "malloc %d byte for watchdog names",
391		    count * WDOG_NAMESIZE);
392
393	wc.wc_names = buf;
394	if (ioctl(fd, WDOGIOC_GWDOGS, &wc) == -1)
395		err(EXIT_FAILURE, "ioctl WDOGIOC_GWDOGS for names");
396
397	count = wc.wc_count;
398	if (count == 0) {
399		printf("No watchdog timers present.\n");
400		free(buf);
401		goto out;
402	}
403
404	printf("Available watchdog timers:\n");
405	for (i = 0, cp = buf; i < count; i++, cp += WDOG_NAMESIZE) {
406		cp[WDOG_NAMESIZE - 1] = '\0';
407		strlcpy(wm.wm_name, cp, sizeof(wm.wm_name));
408
409		if (ioctl(fd, WDOGIOC_GMODE, &wm) == -1)
410			continue;
411		mode = wm.wm_mode & WDOG_MODE_MASK;
412		if (mode == WDOG_MODE_UTICKLE) {
413			if (ioctl(fd, WDOGIOC_GTICKLER, &tickler) == -1)
414				tickler = (pid_t) -1;
415		}
416
417		printf("\t%s, %u second period", cp, wm.wm_period);
418		if (mode != WDOG_MODE_DISARMED) {
419			switch(mode) {
420			case WDOG_MODE_KTICKLE:
421				printf(" [armed, kernel tickle");
422				break;
423			case WDOG_MODE_UTICKLE:
424				printf(" [armed, user tickle");
425				if (tickler != (pid_t) -1)
426					printf(", pid %d", tickler);
427				break;
428			case WDOG_MODE_ETICKLE:
429				printf(" [armed, external tickle");
430				break;
431			}
432			printf("]");
433		}
434		printf("\n");
435	}
436 out:
437	(void)close(fd);
438}
439
440static void
441usage(void)
442{
443
444	fprintf(stderr, "usage: %s\n", getprogname());
445	fprintf(stderr, "       %s -d\n", getprogname());
446	fprintf(stderr, "       %s -e [-A] [-p seconds] timer\n",
447	    getprogname());
448	fprintf(stderr, "       %s -k [-A] [-p seconds] timer\n",
449	    getprogname());
450	fprintf(stderr, "       %s -t\n", getprogname());
451	fprintf(stderr, "       %s -u [-A] [-p seconds] timer\n",
452	    getprogname());
453	fprintf(stderr, "       %s -x [-A] [-p seconds] timer\n",
454	    getprogname());
455
456	exit(1);
457}
458