1/*-
2 * Copyright (c) 2003-2004  Sean M. Kelly <smkelly@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27/*
28 * Software watchdog daemon.
29 */
30
31#include <sys/types.h>
32__FBSDID("$FreeBSD$");
33
34#include <sys/mman.h>
35#include <sys/param.h>
36#include <sys/rtprio.h>
37#include <sys/stat.h>
38#include <sys/time.h>
39#include <sys/watchdog.h>
40
41#include <err.h>
42#include <errno.h>
43#include <fcntl.h>
44#include <libutil.h>
45#include <math.h>
46#include <paths.h>
47#include <signal.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <strings.h>
52#include <sysexits.h>
53#include <unistd.h>
54
55static void	parseargs(int, char *[]);
56static void	sighandler(int);
57static void	watchdog_loop(void);
58static int	watchdog_init(void);
59static int	watchdog_onoff(int onoff);
60static int	watchdog_patpat(u_int timeout);
61static void	usage(void);
62
63static int debugging = 0;
64static int end_program = 0;
65static const char *pidfile = _PATH_VARRUN "watchdogd.pid";
66static u_int timeout = WD_TO_16SEC;
67static u_int passive = 0;
68static int is_daemon = 0;
69static int fd = -1;
70static int nap = 1;
71static char *test_cmd = NULL;
72
73/*
74 * Periodically pat the watchdog, preventing it from firing.
75 */
76int
77main(int argc, char *argv[])
78{
79	struct rtprio rtp;
80	struct pidfh *pfh;
81	pid_t otherpid;
82
83	if (getuid() != 0)
84		errx(EX_SOFTWARE, "not super user");
85
86	parseargs(argc, argv);
87
88	rtp.type = RTP_PRIO_REALTIME;
89	rtp.prio = 0;
90	if (rtprio(RTP_SET, 0, &rtp) == -1)
91		err(EX_OSERR, "rtprio");
92
93	if (watchdog_init() == -1)
94		errx(EX_SOFTWARE, "unable to initialize watchdog");
95
96	if (is_daemon) {
97		if (watchdog_onoff(1) == -1)
98			err(EX_OSERR, "patting the dog");
99
100		pfh = pidfile_open(pidfile, 0600, &otherpid);
101		if (pfh == NULL) {
102			if (errno == EEXIST) {
103				errx(EX_SOFTWARE, "%s already running, pid: %d",
104				    getprogname(), otherpid);
105			}
106			warn("Cannot open or create pidfile");
107		}
108
109		if (debugging == 0 && daemon(0, 0) == -1) {
110			watchdog_onoff(0);
111			pidfile_remove(pfh);
112			err(EX_OSERR, "daemon");
113		}
114
115		signal(SIGHUP, SIG_IGN);
116		signal(SIGINT, sighandler);
117		signal(SIGTERM, sighandler);
118
119		pidfile_write(pfh);
120		if (madvise(0, 0, MADV_PROTECT) != 0)
121			warn("madvise failed");
122		if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0)
123			warn("mlockall failed");
124
125		watchdog_loop();
126
127		/* exiting */
128		pidfile_remove(pfh);
129		return (EX_OK);
130	} else {
131		if (passive)
132			timeout |= WD_PASSIVE;
133		else
134			timeout |= WD_ACTIVE;
135		if (watchdog_patpat(timeout) < 0)
136			err(EX_OSERR, "patting the dog");
137		return (EX_OK);
138	}
139}
140
141/*
142 * Catch signals and begin shutdown process.
143 */
144static void
145sighandler(int signum)
146{
147
148	if (signum == SIGINT || signum == SIGTERM)
149		end_program = 1;
150}
151
152/*
153 * Open the watchdog device.
154 */
155static int
156watchdog_init(void)
157{
158
159	fd = open("/dev/" _PATH_WATCHDOG, O_RDWR);
160	if (fd >= 0)
161		return (0);
162	warn("Could not open watchdog device");
163	return (-1);
164}
165
166/*
167 * Main program loop which is iterated every second.
168 */
169static void
170watchdog_loop(void)
171{
172	struct stat sb;
173	int failed;
174
175	while (end_program != 2) {
176		failed = 0;
177
178		if (test_cmd != NULL)
179			failed = system(test_cmd);
180		else
181			failed = stat("/etc", &sb);
182
183		if (failed == 0)
184			watchdog_patpat(timeout|WD_ACTIVE);
185		sleep(nap);
186
187		if (end_program != 0) {
188			if (watchdog_onoff(0) == 0) {
189				end_program = 2;
190			} else {
191				warnx("Could not stop the watchdog, not exitting");
192				end_program = 0;
193			}
194		}
195	}
196}
197
198/*
199 * Reset the watchdog timer. This function must be called periodically
200 * to keep the watchdog from firing.
201 */
202static int
203watchdog_patpat(u_int t)
204{
205
206	return ioctl(fd, WDIOCPATPAT, &t);
207}
208
209/*
210 * Toggle the kernel's watchdog. This routine is used to enable and
211 * disable the watchdog.
212 */
213static int
214watchdog_onoff(int onoff)
215{
216
217	if (onoff)
218		return watchdog_patpat((timeout|WD_ACTIVE));
219	else
220		return watchdog_patpat(0);
221}
222
223/*
224 * Tell user how to use the program.
225 */
226static void
227usage(void)
228{
229	if (is_daemon)
230		fprintf(stderr, "usage: watchdogd [-d] [-e cmd] [-I file] [-s sleep] [-t timeout]\n");
231	else
232		fprintf(stderr, "usage: watchdog [-d] [-t timeout]\n");
233	exit(EX_USAGE);
234}
235
236/*
237 * Handle the few command line arguments supported.
238 */
239static void
240parseargs(int argc, char *argv[])
241{
242	int c;
243	char *p;
244	double a;
245
246	c = strlen(argv[0]);
247	if (argv[0][c - 1] == 'd')
248		is_daemon = 1;
249	while ((c = getopt(argc, argv,
250	    is_daemon ? "I:de:s:t:?" : "dt:?")) != -1) {
251		switch (c) {
252		case 'I':
253			pidfile = optarg;
254			break;
255		case 'd':
256			debugging = 1;
257			break;
258		case 'e':
259			test_cmd = strdup(optarg);
260			break;
261#ifdef notyet
262		case 'p':
263			passive = 1;
264			break;
265#endif
266		case 's':
267			p = NULL;
268			errno = 0;
269			nap = strtol(optarg, &p, 0);
270			if ((p != NULL && *p != '\0') || errno != 0)
271				errx(EX_USAGE, "-s argument is not a number");
272			break;
273		case 't':
274			p = NULL;
275			errno = 0;
276			a = strtod(optarg, &p);
277			if ((p != NULL && *p != '\0') || errno != 0)
278				errx(EX_USAGE, "-t argument is not a number");
279			if (a < 0)
280				errx(EX_USAGE, "-t argument must be positive");
281			if (a == 0)
282				timeout = WD_TO_NEVER;
283			else
284				timeout = flsll(a * 1e9);
285			if (debugging)
286				printf("Timeout is 2^%d nanoseconds\n",
287				    timeout);
288			break;
289		case '?':
290		default:
291			usage();
292			/* NOTREACHED */
293		}
294	}
295	if (argc != optind)
296		errx(EX_USAGE, "extra arguments.");
297	if (is_daemon && timeout < WD_TO_1SEC)
298		errx(EX_USAGE, "-t argument is less than one second.");
299}
300