1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3  * xenwatchdogd.c
4  *
5  * Watchdog based on Xen hypercall watchdog interface.
6  *
7  * Copyright 2010 Citrix Ltd
8  * Copyright 2024 Leigh Brown <leigh@solinno.co.uk>
9  *
10  */
11 
12 #include <err.h>
13 #include <limits.h>
14 #include "xenctrl.h"
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 #include <signal.h>
21 #include <stdio.h>
22 #include <stdbool.h>
23 #include <getopt.h>
24 
25 #define WDOG_MIN_TIMEOUT 2
26 #define WDOG_MIN_SLEEP 1
27 #define WDOG_EXIT_TIMEOUT 300
28 
29 static xc_interface *h;
30 static volatile bool safeexit = false;
31 static volatile bool done = false;
32 
daemonize(void)33 static void daemonize(void)
34 {
35     switch (fork()) {
36     case -1:
37 	err(EXIT_FAILURE, "fork");
38     case 0:
39 	break;
40     default:
41 	exit(EXIT_SUCCESS);
42     }
43     umask(0);
44     if (setsid() < 0)
45 	err(EXIT_FAILURE, "setsid");
46     if (chdir("/") < 0)
47 	err(EXIT_FAILURE, "chdir /");
48     if (freopen("/dev/null", "r", stdin) == NULL)
49         err(EXIT_FAILURE, "reopen stdin");
50     if(freopen("/dev/null", "w", stdout) == NULL)
51         err(EXIT_FAILURE, "reopen stdout");
52     if(freopen("/dev/null", "w", stderr) == NULL)
53         err(EXIT_FAILURE, "reopen stderr");
54 }
55 
catch_exit(int sig)56 static void catch_exit(int sig)
57 {
58     done = true;
59 }
60 
catch_usr1(int sig)61 static void catch_usr1(int sig)
62 {
63     safeexit = true;
64     done = true;
65 }
66 
usage(int exit_code)67 static void __attribute__((noreturn)) usage(int exit_code)
68 {
69     FILE *out = exit_code ? stderr : stdout;
70 
71     fprintf(out,
72 	"Usage: xenwatchdog [OPTION]... <timeout> [<sleep>]\n"
73 	"  -h, --help\t\tDisplay this help text and exit.\n"
74 	"  -F, --foreground\tRun in foreground.\n"
75 	"  -x, --safe-exit\tDisable watchdog on orderly exit.\n"
76 	"\t\t\tNote: default is to set a %d second timeout on exit.\n\n"
77 	"  timeout\t\tInteger seconds to arm the watchdog each time.\n"
78 	"\t\t\tNote: minimum timeout is %d seconds.\n\n"
79 	"  sleep\t\t\tInteger seconds to sleep between arming the watchdog.\n"
80 	"\t\t\tNote: sleep must be at least %d and less than timeout.\n"
81 	"\t\t\tIf not specified then set to half the timeout.\n",
82 	WDOG_EXIT_TIMEOUT, WDOG_MIN_TIMEOUT, WDOG_MIN_SLEEP
83 	);
84     exit(exit_code);
85 }
86 
parse_secs(const char * arg,const char * what)87 static int parse_secs(const char *arg, const char *what)
88 {
89     char *endptr;
90     unsigned long val;
91 
92     val = strtoul(arg, &endptr, 0);
93     if (val > INT_MAX || *endptr)
94 	errx(EXIT_FAILURE, "invalid %s: '%s'", what, arg);
95 
96     return val;
97 }
98 
main(int argc,char ** argv)99 int main(int argc, char **argv)
100 {
101     int id;
102     int t, s;
103     int ret;
104     bool daemon = true;
105 
106     for ( ;; )
107     {
108 	int option_index = 0, c;
109 	static const struct option long_options[] =
110 	{
111 	    { "help", no_argument, NULL, 'h' },
112 	    { "foreground", no_argument, NULL, 'F' },
113 	    { "safe-exit", no_argument, NULL, 'x' },
114 	    { NULL, 0, NULL, 0 },
115 	};
116 
117 	c = getopt_long(argc, argv, "hFxD", long_options, &option_index);
118 	if (c == -1)
119 	    break;
120 
121 	switch (c)
122 	{
123 	case 'h':
124 	    usage(EXIT_SUCCESS);
125 
126 	case 'F':
127 	    daemon = false;
128 	    break;
129 
130 	case 'x':
131 	    safeexit = true;
132 	    break;
133 
134 	default:
135 	    usage(EXIT_FAILURE);
136 	}
137     }
138 
139     if (argc - optind < 1)
140 	errx(EXIT_FAILURE, "timeout must be specified");
141 
142     if (argc - optind > 2)
143 	errx(EXIT_FAILURE, "too many arguments");
144 
145     t = parse_secs(argv[optind], "timeout");
146     if (t < WDOG_MIN_TIMEOUT)
147 	errx(EXIT_FAILURE, "Error: timeout must be at least %d seconds",
148 			   WDOG_MIN_TIMEOUT);
149 
150     ++optind;
151     if (optind < argc) {
152 	s = parse_secs(argv[optind], "sleep");
153 	if (s < WDOG_MIN_SLEEP)
154 	    errx(EXIT_FAILURE, "Error: sleep must be no less than %d",
155 			       WDOG_MIN_SLEEP);
156 	if (s >= t)
157 	    errx(EXIT_FAILURE, "Error: sleep must be less than timeout");
158     }
159     else
160 	s = t / 2;
161 
162     if (daemon)
163 	daemonize();
164 
165     h = xc_interface_open(NULL, NULL, 0);
166     if (h == NULL)
167 	err(EXIT_FAILURE, "xc_interface_open");
168 
169     if (signal(SIGHUP, &catch_exit) == SIG_ERR)
170 	err(EXIT_FAILURE, "signal");
171     if (signal(SIGINT, &catch_exit) == SIG_ERR)
172 	err(EXIT_FAILURE, "signal");
173     if (signal(SIGQUIT, &catch_exit) == SIG_ERR)
174 	err(EXIT_FAILURE, "signal");
175     if (signal(SIGTERM, &catch_exit) == SIG_ERR)
176 	err(EXIT_FAILURE, "signal");
177     if (signal(SIGUSR1, &catch_usr1) == SIG_ERR)
178 	err(EXIT_FAILURE, "signal");
179 
180     id = xc_watchdog(h, 0, t);
181     if (id <= 0)
182         err(EXIT_FAILURE, "xc_watchdog setup");
183 
184     while (!done) {
185         sleep(s);
186         ret = xc_watchdog(h, id, t);
187         if (ret != 0)
188             err(EXIT_FAILURE, "xc_watchdog");
189     }
190 
191     // Zero seconds timeout will disarm the watchdog timer
192     xc_watchdog(h, id, safeexit ? 0 : WDOG_EXIT_TIMEOUT);
193     return 0;
194 }
195