summaryrefslogtreecommitdiffhomepage
path: root/miscutils/watchdog.c
blob: 44649cae62c7d4e2449836399c3f60952461a2d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/* vi: set sw=4 ts=4: */
/*
 * Mini watchdog implementation for busybox
 *
 * Copyright (C) 2003  Paul Mundt <lethal@linux-sh.org>
 * Copyright (C) 2006  Bernhard Reutner-Fischer <busybox@busybox.net>
 * Copyright (C) 2008  Darius Augulis <augulis.darius@gmail.com>
 *
 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
 */
//config:config WATCHDOG
//config:	bool "watchdog (5.3 kb)"
//config:	default y
//config:	help
//config:	The watchdog utility is used with hardware or software watchdog
//config:	device drivers. It opens the specified watchdog device special file
//config:	and periodically writes a magic character to the device. If the
//config:	watchdog applet ever fails to write the magic character within a
//config:	certain amount of time, the watchdog device assumes the system has
//config:	hung, and will cause the hardware to reboot.
//config:
//config:config FEATURE_WATCHDOG_OPEN_TWICE
//config:	bool "Open watchdog device twice, closing it gracefully in between"
//config:	depends on WATCHDOG
//config:	default n   # this behavior was essentially a hack for a broken driver
//config:	help
//config:	When enabled, the watchdog device is opened and then immediately
//config:	magic-closed, before being opened a second time. This may be necessary
//config:	for some watchdog devices, but can cause spurious warnings in the
//config:	kernel log if the nowayout feature is enabled. Also, if this workaround
//config:	is really needed for you machine to work properly, consider whether
//config:	it should be fixed in the kernel driver instead. Even when disabled,
//config:	the behaviour is easily emulated with a "printf 'V' > /dev/watchdog"
//config:	immediately before starting the busybox watchdog daemon. Say n unless
//config:	you know that you absolutely need this.

//applet:IF_WATCHDOG(APPLET(watchdog, BB_DIR_SBIN, BB_SUID_DROP))

//kbuild:lib-$(CONFIG_WATCHDOG) += watchdog.o

//usage:#define watchdog_trivial_usage
//usage:       "[-t N[ms]] [-T N[ms]] [-F] DEV"
//usage:#define watchdog_full_usage "\n\n"
//usage:       "Periodically write to watchdog device DEV\n"
//usage:     "\n	-T N	Reboot after N seconds if not reset (default 60)"
//usage:     "\n	-t N	Reset every N seconds (default 30)"
//usage:     "\n	-F	Run in foreground"
//usage:     "\n"
//usage:     "\nUse 500ms to specify period in milliseconds"

#include "libbb.h"
#include <linux/types.h> /* for __u32 */
#include <linux/watchdog.h>

#ifndef WDIOC_SETOPTIONS
# define WDIOC_SETOPTIONS 0x5704
#endif
#ifndef WDIOC_SETTIMEOUT
# define WDIOC_SETTIMEOUT 0x5706
#endif
#ifndef WDIOC_GETTIMEOUT
# define WDIOC_GETTIMEOUT 0x5707
#endif
#ifndef WDIOS_ENABLECARD
# define WDIOS_ENABLECARD 2
#endif

static void shutdown_watchdog(void)
{
	static const char V = 'V';
	write(3, &V, 1);  /* Magic, see watchdog-api.txt in kernel */
	close(3);
}

static void shutdown_on_signal(int sig UNUSED_PARAM)
{
	remove_pidfile_std_path_and_ext("watchdog");
	shutdown_watchdog();
	_exit(EXIT_SUCCESS);
}

static void watchdog_open(const char* device)
{
	/* Use known fd # - avoid needing global 'int fd' */
	xmove_fd(xopen(device, O_WRONLY), 3);

#if ENABLE_FEATURE_WATCHDOG_OPEN_TWICE
	/* If the watchdog driver can do something other than cause a reboot
	 * on a timeout, then it's possible this program may be starting from
	 * a state when the watchdog hadn't been previously stopped with
	 * the magic write followed by a close.  In this case the driver may
	 * not start properly, so always do the proper stop first just in case.
	 */
	shutdown_watchdog();

	xmove_fd(xopen(device, O_WRONLY), 3);
#endif
}

int watchdog_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int watchdog_main(int argc UNUSED_PARAM, char **argv)
{
	static const int enable = WDIOS_ENABLECARD;
	static const struct suffix_mult suffixes[] ALIGN_SUFFIX = {
		{ "ms", 1 },
		{ "", 1000 },
		{ "", 0 }
	};

	unsigned opts;
	unsigned stimer_duration; /* how often to restart */
	unsigned htimer_duration = 60000; /* reboots after N ms if not restarted */
	char *st_arg;
	char *ht_arg;

#define OPT_FOREGROUND  (1 << 0)
#define OPT_STIMER      (1 << 1)
#define OPT_HTIMER      (1 << 2)
	opts = getopt32(argv, "^" "Ft:T:" "\0" "=1"/*must have exactly 1 arg*/,
				&st_arg, &ht_arg
	);

	/* We need to daemonize *before* opening the watchdog as many drivers
	 * will only allow one process at a time to do so.  Since daemonizing
	 * is not perfect (child may run before parent finishes exiting), we
	 * can't rely on parent exiting before us (let alone *cleanly* releasing
	 * the watchdog fd -- something else that may not even be allowed).
	 */
	if (!(opts & OPT_FOREGROUND))
		bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);

	/* maybe bb_logenv_override(); here for LOGGING=syslog to work? */

	if (opts & OPT_HTIMER)
		htimer_duration = xatou_sfx(ht_arg, suffixes);
	stimer_duration = htimer_duration / 2;
	if (opts & OPT_STIMER)
		stimer_duration = xatou_sfx(st_arg, suffixes);

	bb_signals(BB_FATAL_SIGS, shutdown_on_signal);

	watchdog_open(argv[optind]);

	/* WDIOC_SETTIMEOUT takes seconds, not milliseconds */
	htimer_duration = htimer_duration / 1000;
	ioctl_or_warn(3, WDIOC_SETOPTIONS, (void*) &enable);
	ioctl_or_warn(3, WDIOC_SETTIMEOUT, &htimer_duration);
#if 0
	ioctl_or_warn(3, WDIOC_GETTIMEOUT, &htimer_duration);
	printf("watchdog: SW timer is %dms, HW timer is %ds\n",
		stimer_duration, htimer_duration);
#endif

	write_pidfile_std_path_and_ext("watchdog");

	while (1) {
		/*
		 * Make sure we clear the counter before sleeping,
		 * as the counter value is undefined at this point -- PFM
		 */
		write(3, "", 1); /* write zero byte */
		msleep(stimer_duration);
	}
	return EXIT_SUCCESS; /* - not reached, but gcc 4.2.1 is too dumb! */
}