]>
Commit | Line | Data |
---|---|---|
6263c81d | 1 | #define _GNU_SOURCE |
da8f8bbc DM |
2 | #include <stdio.h> |
3 | #include <stdlib.h> | |
4 | #include <unistd.h> | |
5 | #include <fcntl.h> | |
6 | #include <string.h> | |
1fe42db7 | 7 | #include <errno.h> |
98099e4f | 8 | #include <time.h> |
da8f8bbc | 9 | #include <sys/ioctl.h> |
7336614a DM |
10 | #include <sys/types.h> |
11 | #include <sys/stat.h> | |
da8f8bbc DM |
12 | #include <sys/socket.h> |
13 | #include <sys/un.h> | |
14 | #include <sys/epoll.h> | |
98099e4f DM |
15 | #include <signal.h> |
16 | #include <sys/signalfd.h> | |
da8f8bbc DM |
17 | |
18 | #include <linux/types.h> | |
19 | #include <linux/watchdog.h> | |
20 | ||
06b589da | 21 | #define WD_SOCK_PATH "/run/watchdog-mux.sock" |
98099e4f DM |
22 | #define WD_ACTIVE_MARKER "/run/watchdog-mux.active" |
23 | ||
1dd1d6cd | 24 | #define LISTEN_BACKLOG 32 |
92763a2e | 25 | |
da8f8bbc DM |
26 | #define MAX_EVENTS 10 |
27 | ||
28 | #define WATCHDOG_DEV "/dev/watchdog" | |
29 | ||
9f449463 TL |
30 | #define JOURNALCTL_BIN "/bin/journalctl" |
31 | ||
da8f8bbc | 32 | int watchdog_fd = -1; |
5ce9f244 DM |
33 | int watchdog_timeout = 10; |
34 | int client_watchdog_timeout = 60; | |
0da2e042 | 35 | int update_watchdog = 1; |
4915a0e9 DM |
36 | |
37 | typedef struct { | |
38 | int fd; | |
98099e4f | 39 | time_t time; |
4178d9ea | 40 | int magic_close; |
4915a0e9 DM |
41 | } wd_client_t; |
42 | ||
43 | #define MAX_CLIENTS 100 | |
44 | ||
45 | static wd_client_t client_list[MAX_CLIENTS]; | |
46 | ||
47 | static wd_client_t * | |
98099e4f | 48 | alloc_client(int fd, time_t time) |
4915a0e9 DM |
49 | { |
50 | int i; | |
51 | ||
52 | for (i = 0; i < MAX_CLIENTS; i++) { | |
53 | if (client_list[i].fd == 0) { | |
4915a0e9 | 54 | client_list[i].fd = fd; |
98099e4f | 55 | client_list[i].time = time; |
4178d9ea | 56 | client_list[i].magic_close = 0; |
4915a0e9 DM |
57 | return &client_list[i]; |
58 | } | |
59 | } | |
60 | ||
61 | return NULL; | |
62 | } | |
63 | ||
64 | static void | |
65 | free_client(wd_client_t *wd_client) | |
66 | { | |
67 | if (!wd_client) | |
68 | return; | |
69 | ||
98099e4f | 70 | wd_client->time = 0; |
4915a0e9 | 71 | wd_client->fd = 0; |
4178d9ea | 72 | wd_client->magic_close = 0; |
4915a0e9 DM |
73 | } |
74 | ||
98099e4f DM |
75 | static int |
76 | active_client_count(void) | |
77 | { | |
78 | int i, count = 0; | |
79 | ||
80 | for (i = 0; i < MAX_CLIENTS; i++) { | |
81 | if (client_list[i].fd != 0 && client_list[i].time != 0) { | |
82 | count++; | |
83 | } | |
84 | } | |
0da2e042 | 85 | |
98099e4f DM |
86 | return count; |
87 | } | |
88 | ||
0da2e042 | 89 | static void |
da8f8bbc DM |
90 | watchdog_close(void) |
91 | { | |
92 | if (watchdog_fd != -1) { | |
93 | if (write(watchdog_fd, "V", 1) == -1) { | |
94 | perror("write magic watchdog close"); | |
95 | } | |
96 | if (close(watchdog_fd) == -1) { | |
97 | perror("write magic watchdog close"); | |
98 | } | |
99 | } | |
100 | ||
101 | watchdog_fd = -1; | |
102 | } | |
9f449463 TL |
103 | |
104 | static void | |
105 | sync_journal_unsafe(void) | |
106 | { | |
107 | ||
108 | pid_t child = fork(); | |
109 | ||
110 | // do not care about fork error or collecting the childs exit status, | |
111 | // we are resetting soon anyway and just want to sync out the journal | |
112 | if (child == 0) { | |
56d4c7a5 TL |
113 | execl(JOURNALCTL_BIN, JOURNALCTL_BIN, "--sync", NULL); |
114 | exit(-1); | |
9f449463 TL |
115 | } |
116 | } | |
117 | ||
0da2e042 | 118 | int |
da8f8bbc DM |
119 | main(void) |
120 | { | |
da8f8bbc DM |
121 | struct sockaddr_un my_addr, peer_addr; |
122 | socklen_t peer_addr_size; | |
123 | struct epoll_event ev, events[MAX_EVENTS]; | |
f8a3fc80 | 124 | int listen_sock, nfds, epollfd, sigfd; |
a5219d62 | 125 | |
da8f8bbc | 126 | struct stat fs; |
98099e4f DM |
127 | |
128 | if (stat(WD_ACTIVE_MARKER, &fs) == 0) { | |
129 | fprintf(stderr, "watchdog active - unable to restart watchdog-mux\n"); | |
0da2e042 | 130 | exit(EXIT_FAILURE); |
98099e4f | 131 | } |
b7d5be18 DM |
132 | |
133 | /* if you want to debug, set options in /lib/modprobe.d/aliases.conf | |
134 | * options softdog soft_noboot=1 | |
135 | */ | |
da8f8bbc | 136 | if (stat(WATCHDOG_DEV, &fs) == -1) { |
6263c81d DM |
137 | char *wd_module = getenv("WATCHDOG_MODULE"); |
138 | if (wd_module) { | |
139 | char *cmd = NULL; | |
140 | if ((asprintf(&cmd, "modprobe -q %s", wd_module) == -1)) { | |
141 | perror("assemble modprobe command failed"); | |
142 | exit(EXIT_FAILURE); | |
143 | } | |
4a53064f | 144 | fprintf(stderr, "Loading watchdog module '%s'\n", wd_module); |
6263c81d | 145 | system(cmd); |
8415603b | 146 | free(cmd); |
6263c81d DM |
147 | } else { |
148 | system("modprobe -q softdog"); // load softdog by default | |
149 | } | |
da8f8bbc DM |
150 | } |
151 | ||
152 | if ((watchdog_fd = open(WATCHDOG_DEV, O_WRONLY)) == -1) { | |
56d4c7a5 TL |
153 | perror("watchdog open"); |
154 | exit(EXIT_FAILURE); | |
da8f8bbc | 155 | } |
0da2e042 | 156 | |
da8f8bbc DM |
157 | if (ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &watchdog_timeout) == -1) { |
158 | perror("watchdog set timeout"); | |
159 | watchdog_close(); | |
160 | exit(EXIT_FAILURE); | |
161 | } | |
162 | ||
163 | /* read and log watchdog identity */ | |
164 | struct watchdog_info wdinfo; | |
165 | if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &wdinfo) == -1) { | |
166 | perror("read watchdog info"); | |
167 | watchdog_close(); | |
168 | exit(EXIT_FAILURE); | |
169 | } | |
170 | ||
171 | wdinfo.identity[sizeof(wdinfo.identity) - 1] = 0; // just to be sure | |
56d4c7a5 | 172 | fprintf(stderr, "Watchdog driver '%s', version %x\n", wdinfo.identity, wdinfo.firmware_version); |
da8f8bbc | 173 | |
f8a3fc80 TL |
174 | /* always unlink socket path then create socket */ |
175 | unlink(WD_SOCK_PATH); | |
da8f8bbc | 176 | |
f8a3fc80 TL |
177 | listen_sock = socket(AF_UNIX, SOCK_STREAM, 0); |
178 | if (listen_sock == -1) { | |
56d4c7a5 TL |
179 | perror("socket create"); |
180 | exit(EXIT_FAILURE); | |
f8a3fc80 TL |
181 | } |
182 | memset(&my_addr, 0, sizeof(struct sockaddr_un)); | |
183 | my_addr.sun_family = AF_UNIX; | |
184 | strncpy(my_addr.sun_path, WD_SOCK_PATH, sizeof(my_addr.sun_path) - 1); | |
185 | ||
56d4c7a5 TL |
186 | if (bind(listen_sock, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_un)) == -1) { |
187 | perror("socket bind"); | |
188 | exit(EXIT_FAILURE); | |
f8a3fc80 | 189 | } |
e99d3682 | 190 | |
f8a3fc80 | 191 | if (listen(listen_sock, LISTEN_BACKLOG) == -1) { |
56d4c7a5 TL |
192 | perror("socket listen"); |
193 | goto err; | |
da8f8bbc | 194 | } |
f8a3fc80 | 195 | |
da8f8bbc DM |
196 | epollfd = epoll_create(10); |
197 | if (epollfd == -1) { | |
198 | perror("epoll_create"); | |
199 | goto err; | |
200 | } | |
201 | ||
202 | ev.events = EPOLLIN; | |
98099e4f | 203 | ev.data.ptr = alloc_client(listen_sock, 0); |
da8f8bbc | 204 | if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) { |
98099e4f DM |
205 | perror("epoll_ctl add listen_sock"); |
206 | goto err; | |
207 | } | |
208 | ||
209 | sigset_t mask; | |
210 | sigemptyset(&mask); | |
211 | sigaddset(&mask, SIGINT); | |
212 | sigaddset(&mask, SIGTERM); | |
213 | sigaddset(&mask, SIGHUP); | |
0da2e042 | 214 | |
98099e4f | 215 | sigprocmask(SIG_BLOCK, &mask, NULL); |
0da2e042 | 216 | |
98099e4f DM |
217 | if ((sigfd = signalfd(-1, &mask, SFD_NONBLOCK)) < 0) { |
218 | perror("unable to open signalfd"); | |
da8f8bbc DM |
219 | goto err; |
220 | } | |
221 | ||
98099e4f DM |
222 | ev.events = EPOLLIN; |
223 | ev.data.ptr = alloc_client(sigfd, 0); | |
224 | if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sigfd, &ev) == -1) { | |
225 | perror("epoll_ctl add sigfd"); | |
226 | goto err; | |
227 | } | |
0da2e042 | 228 | |
da8f8bbc | 229 | for (;;) { |
1fe42db7 | 230 | nfds = epoll_wait(epollfd, events, MAX_EVENTS, 1000); |
da8f8bbc | 231 | if (nfds == -1) { |
1fe42db7 DM |
232 | if (errno == EINTR) |
233 | continue; | |
0da2e042 | 234 | |
da8f8bbc DM |
235 | perror("epoll_pwait"); |
236 | goto err; | |
237 | } | |
238 | ||
1fe42db7 DM |
239 | if (nfds == 0) { // timeout |
240 | ||
5ce9f244 DM |
241 | // check for timeouts |
242 | if (update_watchdog) { | |
243 | int i; | |
244 | time_t ctime = time(NULL); | |
245 | for (i = 0; i < MAX_CLIENTS; i++) { | |
56d4c7a5 TL |
246 | if ( |
247 | client_list[i].fd != 0 | |
248 | && client_list[i].time != 0 | |
249 | && ((ctime - client_list[i].time) > client_watchdog_timeout) | |
250 | ) { | |
5ce9f244 | 251 | update_watchdog = 0; |
0da2e042 | 252 | fprintf(stderr, "client watchdog expired - disable watchdog updates\n"); |
5ce9f244 DM |
253 | } |
254 | } | |
255 | } | |
256 | ||
257 | if (update_watchdog) { | |
258 | if (ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0) == -1) { | |
259 | perror("watchdog update failed"); | |
260 | } | |
1fe42db7 | 261 | } |
0da2e042 | 262 | |
1fe42db7 DM |
263 | continue; |
264 | } | |
265 | ||
115805fd DM |
266 | if (!update_watchdog) |
267 | break; | |
0da2e042 | 268 | |
98099e4f | 269 | int terminate = 0; |
0da2e042 | 270 | |
da8f8bbc DM |
271 | int n; |
272 | for (n = 0; n < nfds; ++n) { | |
4915a0e9 DM |
273 | wd_client_t *wd_client = events[n].data.ptr; |
274 | if (wd_client->fd == listen_sock) { | |
da8f8bbc DM |
275 | int conn_sock = accept(listen_sock, (struct sockaddr *) &peer_addr, &peer_addr_size); |
276 | if (conn_sock == -1) { | |
277 | perror("accept"); | |
278 | goto err; // fixme | |
279 | } | |
280 | if (fcntl(conn_sock, F_SETFL, O_NONBLOCK) == -1) { | |
281 | perror("setnonblocking"); | |
282 | goto err; // fixme | |
283 | } | |
284 | ||
98099e4f | 285 | wd_client_t *new_client = alloc_client(conn_sock, time(NULL)); |
4915a0e9 DM |
286 | if (new_client == NULL) { |
287 | fprintf(stderr, "unable to alloc wd_client structure\n"); | |
288 | goto err; // fixme; | |
289 | } | |
0da2e042 | 290 | |
98099e4f | 291 | mkdir(WD_ACTIVE_MARKER, 0600); |
0da2e042 | 292 | |
da8f8bbc | 293 | ev.events = EPOLLIN; |
4915a0e9 | 294 | ev.data.ptr = new_client; |
da8f8bbc DM |
295 | if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) { |
296 | perror("epoll_ctl: add conn_sock"); | |
0da2e042 | 297 | goto err; // fixme |
da8f8bbc | 298 | } |
98099e4f DM |
299 | } else if (wd_client->fd == sigfd) { |
300 | ||
301 | /* signal handling */ | |
302 | ||
303 | int rv = 0; | |
304 | struct signalfd_siginfo si; | |
305 | ||
306 | if ((rv = read(sigfd, &si, sizeof(si))) && rv >= 0) { | |
307 | if (si.ssi_signo == SIGHUP) { | |
308 | perror("got SIGHUP - ignored"); | |
309 | } else { | |
310 | terminate = 1; | |
311 | fprintf(stderr, "got terminate request\n"); | |
312 | } | |
313 | } | |
0da2e042 | 314 | |
da8f8bbc DM |
315 | } else { |
316 | char buf[4096]; | |
4915a0e9 | 317 | int cfd = wd_client->fd; |
0da2e042 | 318 | |
da8f8bbc DM |
319 | ssize_t bytes = read(cfd, buf, sizeof(buf)); |
320 | if (bytes == -1) { | |
321 | perror("read"); | |
0da2e042 | 322 | goto err; // fixme |
da8f8bbc | 323 | } else if (bytes > 0) { |
4178d9ea DM |
324 | int i; |
325 | for (i = 0; i < bytes; i++) { | |
326 | if (buf[i] == 'V') { | |
327 | wd_client->magic_close = 1; | |
328 | } else { | |
329 | wd_client->magic_close = 0; | |
330 | } | |
331 | } | |
332 | wd_client->time = time(NULL); | |
da8f8bbc DM |
333 | } else { |
334 | if (events[n].events & EPOLLHUP || events[n].events & EPOLLERR) { | |
4178d9ea | 335 | //printf("GOT %016x event\n", events[n].events); |
da8f8bbc DM |
336 | if (epoll_ctl(epollfd, EPOLL_CTL_DEL, cfd, NULL) == -1) { |
337 | perror("epoll_ctl: del conn_sock"); | |
0da2e042 | 338 | goto err; // fixme |
da8f8bbc DM |
339 | } |
340 | if (close(cfd) == -1) { | |
341 | perror("close conn_sock"); | |
0da2e042 | 342 | goto err; // fixme |
da8f8bbc | 343 | } |
98099e4f | 344 | |
4178d9ea | 345 | if (!wd_client->magic_close) { |
5ce9f244 | 346 | fprintf(stderr, "client did not stop watchdog - disable watchdog updates\n"); |
9f449463 | 347 | sync_journal_unsafe(); |
5ce9f244 | 348 | update_watchdog = 0; |
4178d9ea DM |
349 | } else { |
350 | free_client(wd_client); | |
351 | } | |
0da2e042 | 352 | |
98099e4f DM |
353 | if (!active_client_count()) { |
354 | rmdir(WD_ACTIVE_MARKER); | |
355 | } | |
da8f8bbc DM |
356 | } |
357 | } | |
358 | } | |
359 | } | |
98099e4f DM |
360 | if (terminate) |
361 | break; | |
da8f8bbc DM |
362 | } |
363 | ||
98099e4f DM |
364 | int active_count = active_client_count(); |
365 | if (active_count > 0) { | |
366 | fprintf(stderr, "exit watchdog-mux with active connections\n"); | |
9f449463 | 367 | sync_journal_unsafe(); |
98099e4f DM |
368 | } else { |
369 | fprintf(stderr, "clean exit\n"); | |
370 | watchdog_close(); | |
371 | } | |
a5219d62 TL |
372 | |
373 | unlink(WD_SOCK_PATH); | |
374 | ||
da8f8bbc DM |
375 | exit(EXIT_SUCCESS); |
376 | ||
377 | err: | |
a5219d62 | 378 | unlink(WD_SOCK_PATH); |
06b589da | 379 | |
da8f8bbc DM |
380 | exit(EXIT_FAILURE); |
381 | } |