1 /*
2 * Copyright (C) 2013 Lars Marowsky-Bree <lmb@suse.com>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include "sbd.h"
20 #ifdef __linux__
21 #include <sys/sysmacros.h>
22 #endif
23 /* Musl-specific: basename is in libgen.h, and musl lacks __GLIBC__ */
24 #ifndef __GLIBC__
25 #include <libgen.h>
26 #endif
27 #include <dirent.h>
28 #include <limits.h>
29
30 /* possibly tunable defaults regarding watchdog operation
31 are found in sbd-common.c
32 */
33
34 /* Global, non-tunable variables: */
35 int watchdogfd = -1;
36 char *watchdogdev = NULL;
37 bool watchdogdev_is_default = false;
38 bool do_calculate_timeout_watchdog_warn = true;
39 int timeout_watchdog_warn =
40 calculate_timeout_watchdog_warn(SBD_WATCHDOG_TIMEOUT_DEFAULT);
41
42 #define MAX_WATCHDOGS 64
43 #define SYS_CLASS_WATCHDOG "/sys/class/watchdog"
44 #define SYS_CHAR_DEV_DIR "/sys/dev/char"
45 #define WATCHDOG_NODEDIR "/dev/"
46
47 static bool
48 is_watchdog(dev_t device)
49 {
50 static int num_watchdog_devs = 0;
51 static dev_t watchdog_devs[MAX_WATCHDOGS];
52 struct dirent *entry;
53 int i;
54
55 /* populate on first call */
56 if (num_watchdog_devs == 0) {
57 DIR *dp;
58
59 watchdog_devs[0] = makedev(10,130);
60 num_watchdog_devs = 1;
61
62 /* get additional devices from /sys/class/watchdog */
63 dp = opendir(SYS_CLASS_WATCHDOG);
64 if (dp) {
65 while ((entry = readdir(dp))) {
66 if (entry->d_type == DT_LNK) {
67 FILE *file;
68 char entry_name[NAME_MAX+sizeof(SYS_CLASS_WATCHDOG)+5];
69
70 snprintf(entry_name, sizeof(entry_name),
71 SYS_CLASS_WATCHDOG "/%s/dev", entry->d_name);
72 file = fopen(entry_name, "r");
73 if (file) {
74 int major, minor;
75
76 if (fscanf(file, "%d:%d", &major, &minor) == 2) {
77 watchdog_devs[num_watchdog_devs++] =
78 makedev(major, minor);
79 }
80 fclose(file);
81 if (num_watchdog_devs == MAX_WATCHDOGS) {
82 break;
83 }
84 }
85 }
86 }
87 closedir(dp);
88 }
89 }
90
91 for (i=0; i < num_watchdog_devs; i++) {
92 if (device == watchdog_devs[i]) {
93 return true;
94 }
95 }
96 return false;
97 }
98
99 static int
100 watchdog_init_interval_fd(int wdfd, int timeout)
101 {
102 if (ioctl(wdfd, WDIOC_SETTIMEOUT, &timeout) < 0) {
103 cl_perror( "WDIOC_SETTIMEOUT"
104 ": Failed to set watchdog timer to %d seconds.",
105 timeout);
106 cl_log(LOG_CRIT, "Please validate your watchdog configuration!");
107 cl_log(LOG_CRIT, "Choose a different watchdog driver or specify "
108 "-T to skip this if you are completely sure.");
109 return -1;
110 }
111 return 0;
112 }
113
114 int
115 watchdog_init_interval(void)
116 {
117 if (watchdogfd < 0) {
118 return 0;
119 }
120
121 if (watchdog_set_timeout == 0) {
122 cl_log(LOG_INFO,
123 "NOT setting watchdog timeout on explicit user request!");
124 return 0;
125 }
126
127 if (watchdog_init_interval_fd(watchdogfd, timeout_watchdog) < 0) {
128 return -1;
129 }
130 cl_log(LOG_INFO, "Set watchdog timeout to %d seconds.", timeout_watchdog);
131 return 0;
132 }
133
134 static int
135 watchdog_tickle_fd(int wdfd, char *wddev)
136 {
137 if (write(wdfd, "", 1) != 1) {
138 cl_perror("Watchdog write failure: %s!", wddev);
139 return -1;
140 }
141 return 0;
142 }
143
144 int
145 watchdog_tickle(void)
146 {
147 if (watchdogfd >= 0) {
148 return watchdog_tickle_fd(watchdogfd, watchdogdev);
149 }
150 return 0;
151 }
152
153 static int
154 watchdog_init_fd(char *wddev, int timeout)
155 {
156 int wdfd;
157
158 wdfd = open(wddev, O_WRONLY);
159 if (wdfd >= 0) {
160 if (((timeout >= 0) &&
161 (watchdog_init_interval_fd(wdfd, timeout) < 0)) ||
162 (watchdog_tickle_fd(wdfd, wddev) < 0)) {
163 close(wdfd);
164 return -1;
165 }
166 } else {
167 struct stat statbuf;
168
169 if(!stat(wddev, &statbuf) && S_ISCHR(statbuf.st_mode) &&
170 is_watchdog(statbuf.st_rdev)) {
171 cl_perror("Cannot open watchdog device '%s'", wddev);
172 } else {
173 cl_perror("Seems as if '%s' isn't a valid watchdog-device", wddev);
174 }
175 return -1;
176 }
177 return wdfd;
178 }
179
180 int
181 watchdog_init(void)
182 {
183 if (watchdogfd < 0 && watchdogdev != NULL) {
184 int timeout = timeout_watchdog;
185
186 if (watchdog_set_timeout == 0) {
187 cl_log(LOG_INFO,
188 "NOT setting watchdog timeout on explicit user request!");
189 timeout = -1;
190 }
191 watchdogfd = watchdog_init_fd(watchdogdev, timeout);
192 if (watchdogfd >= 0) {
193 cl_log(LOG_NOTICE, "Using watchdog device '%s'", watchdogdev);
194 if (watchdog_set_timeout) {
195 cl_log(LOG_INFO, "Set watchdog timeout to %d seconds.",
196 timeout_watchdog);
197 }
198 } else {
199 return -1;
200 }
201 }
202 return 0;
203 }
204
205 static void
206 watchdog_close_fd(int wdfd, char *wddev, bool disarm)
207 {
208 if (disarm) {
209 int r;
210 int flags = WDIOS_DISABLECARD;;
211
212 /* Explicitly disarm it */
213 r = ioctl(wdfd, WDIOC_SETOPTIONS, &flags);
214 if (r < 0) {
215 cl_perror("Failed to disable hardware watchdog %s", wddev);
216 }
217
218 /* To be sure, use magic close logic, too */
219 for (;;) {
220 if (write(wdfd, "V", 1) > 0) {
221 break;
222 }
223 cl_perror("Cannot disable watchdog device %s", wddev);
224 }
225 }
226
227 if (close(wdfd) < 0) {
228 cl_perror("Watchdog close(%d) failed", wdfd);
229 }
230 }
231
232 void
233 watchdog_close(bool disarm)
234 {
235 if (watchdogfd < 0) {
236 return;
237 }
238
239 watchdog_close_fd(watchdogfd, watchdogdev, disarm);
240 watchdogfd = -1;
241 }
242
243 struct watchdog_list_item {
244 dev_t dev;
245 char *dev_node;
246 char *dev_ident;
247 char *dev_driver;
248 pid_t busy_pid;
249 char *busy_name;
250 struct watchdog_list_item *next;
251 };
252
253 struct link_list_item {
254 char *dev_node;
255 char *link_name;
256 struct link_list_item *next;
257 };
258
259 static struct watchdog_list_item *watchdog_list = NULL;
260 static int watchdog_list_items = 0;
261
262 static void
263 watchdog_populate_list(void)
264 {
265 struct dirent *entry;
266 char entry_name[sizeof(WATCHDOG_NODEDIR)+NAME_MAX];
267 DIR *dp;
268 char buf[NAME_MAX+sizeof(WATCHDOG_NODEDIR)] = "";
269 struct link_list_item *link_list = NULL;
270
|
(1) Event cond_false: |
Condition "watchdog_list != NULL", taking false branch. |
271 if (watchdog_list != NULL) {
272 return;
|
(2) Event if_end: |
End of if statement. |
273 }
274
275 /* search for watchdog nodes in /dev */
276 dp = opendir(WATCHDOG_NODEDIR);
|
(3) Event cond_true: |
Condition "dp", taking true branch. |
277 if (dp) {
278 /* first go for links and memorize them */
|
(4) Event cond_true: |
Condition "entry = readdir(dp)", taking true branch. |
|
(10) Event cond_true: |
Condition "entry = readdir(dp)", taking true branch. |
279 while ((entry = readdir(dp))) {
|
(5) Event cond_true: |
Condition "entry->d_type == DT_LNK", taking true branch. |
|
(11) Event cond_true: |
Condition "entry->d_type == DT_LNK", taking true branch. |
280 if (entry->d_type == DT_LNK) {
281 int len;
282
283 snprintf(entry_name, sizeof(entry_name),
284 WATCHDOG_NODEDIR "%s", entry->d_name);
285
286 /* realpath(entry_name, buf) unfortunately does a stat on
287 * target so we can't really use it to check if links stay
288 * within /dev without triggering e.g. AVC-logs (with
289 * SELinux policy that just allows stat within /dev).
290 * Without canonicalization that doesn't actually touch the
291 * filesystem easily available introduce some limitations
292 * for simplicity:
293 * - just simple path without '..'
294 * - just one level of symlinks (avoid e.g. loop-checking)
295 */
296 len = readlink(entry_name, buf, sizeof(buf) - 1);
|
(6) Event cond_false: |
Condition "len < 1", taking false branch. |
|
(7) Event cond_true: |
Condition "len > 253UL /* sizeof (buf) - sizeof ("/dev/") - 1 - 1 */", taking true branch. |
|
(12) Event cond_false: |
Condition "len < 1", taking false branch. |
|
(13) Event cond_false: |
Condition "len > 253UL /* sizeof (buf) - sizeof ("/dev/") - 1 - 1 */", taking false branch. |
297 if ((len < 1) ||
298 (len > sizeof(buf) - sizeof(WATCHDOG_NODEDIR) -1 - 1)) {
|
(8) Event continue: |
Continuing loop. |
299 continue;
|
(14) Event if_end: |
End of if statement. |
300 }
301 buf[len] = '\0';
|
(15) Event cond_true: |
Condition "buf[0] != '/'", taking true branch. |
302 if (buf[0] != '/') {
303 memmove(&buf[sizeof(WATCHDOG_NODEDIR)-1], buf, len+1);
|
(16) Event string_copy: |
Calling "memcpy" copies a source string ""/dev/"" to "buf". |
|
(17) Event string_null_source: |
The argument "buf" will not be null-terminated, because the length of source string ""/dev/"" (5 characters) is greater than or equal to the size argument (5). |
| Also see events: |
[string_null] |
304 memcpy(buf, WATCHDOG_NODEDIR, sizeof(WATCHDOG_NODEDIR)-1);
305 len += sizeof(WATCHDOG_NODEDIR)-1;
306 }
|
(18) Event string_null: |
Passing unterminated string "buf" to "strstr", which expects a null-terminated string. |
| Also see events: |
[string_copy][string_null_source] |
307 if (strstr(buf, "/../") ||
308 strncmp(WATCHDOG_NODEDIR, buf,
309 sizeof(WATCHDOG_NODEDIR)-1)) {
310 continue;
311 } else {
312 /* just memorize to avoid statting the target - SELinux */
313 struct link_list_item *lli =
314 calloc(1, sizeof(struct link_list_item));
315
316 if (lli == NULL) {
317 break;
318 }
319 lli->dev_node = strdup(buf);
320 lli->link_name = strdup(entry_name);
321 if ((lli->dev_node == NULL) || (lli->link_name == NULL)) {
322 free(lli->dev_node);
323 free(lli->link_name);
324 free(lli);
325 break;
326 }
327 lli->next = link_list;
328 link_list = lli;
329 }
330 }
|
(9) Event loop: |
Looping back. |
331 }
332
333 rewinddir(dp);
334
335 while ((entry = readdir(dp))) {
336 if (entry->d_type == DT_CHR) {
337 struct stat statbuf;
338
339 snprintf(entry_name, sizeof(entry_name),
340 WATCHDOG_NODEDIR "%s", entry->d_name);
341 if(!stat(entry_name, &statbuf) && S_ISCHR(statbuf.st_mode) &&
342 is_watchdog(statbuf.st_rdev)) {
343
344 int wdfd;
345 struct watchdog_list_item *wdg =
346 calloc(1, sizeof(struct watchdog_list_item));
347 int len;
348 struct link_list_item *tmp_list = NULL;
349
350 if (wdg == NULL) {
351 break;
352 }
353
354 wdg->dev = statbuf.st_rdev;
355 wdg->dev_node = strdup(entry_name);
356 if (wdg->dev_node == NULL) {
357 free(wdg);
358 break;
359 }
360 wdg->next = watchdog_list;
361 watchdog_list = wdg;
362 watchdog_list_items++;
363
364 wdfd = watchdog_init_fd(entry_name, -1);
365 if (wdfd >= 0) {
366 struct watchdog_info ident;
367
368 ident.identity[0] = '\0';
369 ioctl(wdfd, WDIOC_GETSUPPORT, &ident);
370 watchdog_close_fd(wdfd, entry_name, true);
371 if (ident.identity[0]) {
372 wdg->dev_ident = strdup((char *) ident.identity);
373 }
374 }
375
376 snprintf(entry_name, sizeof(entry_name),
377 SYS_CHAR_DEV_DIR "/%d:%d/device/driver",
378 major(wdg->dev), minor(wdg->dev));
379 len = readlink(entry_name, buf, sizeof(buf) - 1);
380 if (len > 0) {
381 buf[len] = '\0';
382 wdg->dev_driver = strdup(basename(buf));
383 } else if ((wdg->dev_ident) &&
384 (strcmp(wdg->dev_ident,
385 "Software Watchdog") == 0)) {
386 wdg->dev_driver = strdup("softdog");
387 }
388
389 /* create dupes if we have memorized links
390 * to this node
391 */
392 for (tmp_list = link_list; tmp_list;
393 tmp_list = tmp_list->next) {
394 if (!strcmp(tmp_list->dev_node,
395 wdg->dev_node)) {
396 struct watchdog_list_item *dupe_wdg =
397 calloc(1, sizeof(struct watchdog_list_item));
398
399 if (dupe_wdg == NULL) {
400 break;
401 }
402 /* as long as we never purge watchdog_list
403 * there is no need to dupe strings
404 */
405 *dupe_wdg = *wdg;
406 dupe_wdg->dev_node = strdup(tmp_list->link_name);
407 if (dupe_wdg->dev_node == NULL) {
408 free(dupe_wdg);
409 break;
410 }
411 dupe_wdg->next = watchdog_list;
412 watchdog_list = dupe_wdg;
413 watchdog_list_items++;
414 }
415 /* for performance reasons we could remove
416 * the link_list entry
417 */
418 }
419 }
420 }
421 }
422
423 closedir(dp);
424 }
425
426 /* cleanup link list */
427 while (link_list) {
428 struct link_list_item *tmp_list = link_list;
429
430 link_list = link_list->next;
431 free(tmp_list->dev_node);
432 free(tmp_list->link_name);
433 free(tmp_list);
434 }
435 }
436
437 static void
438 watchdog_checkbusy()
439 {
440 DIR *dproc;
441 struct dirent *entry;
442
443 dproc = opendir("/proc");
444 if (!dproc) {
445 /* no proc directory to search through */
446 return;
447 }
448
449 while ((entry = readdir(dproc)) != NULL) {
450 pid_t local_pid;
451 char *leftover;
452 DIR *dpid;
453 char procpath[NAME_MAX+10] = { 0 };
454
455 if (entry->d_name[0] == '.') {
456 continue;
457 }
458
459 local_pid = strtol(entry->d_name, &leftover, 10);
460 if (leftover[0] != '\0')
461 continue;
462
463 snprintf(procpath, sizeof(procpath), "/proc/%s/fd", entry->d_name);
464 dpid = opendir(procpath);
465 if (!dpid) {
466 /* silently continue - might be just a race */
467 continue;
468 }
469 while ((entry = readdir(dpid)) != NULL) {
470 struct watchdog_list_item *wdg;
471 char entry_name[sizeof(procpath)+NAME_MAX+1] = { 0 };
472 char buf[NAME_MAX+1] = { 0 };
473 int len;
474
475 if (entry->d_type != DT_LNK) {
476 continue;
477 }
478 snprintf(entry_name, sizeof(entry_name),
479 "%s/%s", procpath, entry->d_name);
480 len = readlink(entry_name, buf, sizeof(buf) - 1);
481 if (len < 1) {
482 continue;
483 }
484 buf[len] = '\0';
485 for (wdg = watchdog_list; wdg != NULL; wdg = wdg->next) {
486 if (!strcmp(buf, wdg->dev_node)) {
487 char name[16];
488 FILE *file;
489
490 wdg->busy_pid = local_pid;
491 snprintf(procpath, sizeof(procpath), "/proc/%d/status",
492 local_pid);
493 file = fopen(procpath, "r");
494 if (file) {
495 if (fscanf(file, "Name:\t%15[a-zA-Z0-9 _-]",
496 name) == 1) {
497 wdg->busy_name = strdup(name);
498 }
499 fclose(file);
500 }
501 }
502 }
503 }
504 closedir(dpid);
505 }
506
507 closedir(dproc);
508
509 return;
510 }
511
512 int watchdog_info(void)
513 {
514 struct watchdog_list_item *wdg;
515 int wdg_cnt = 0;
516
517 watchdog_populate_list();
518 watchdog_checkbusy();
519 printf("\nDiscovered %d watchdog devices:\n", watchdog_list_items);
520 for (wdg = watchdog_list; wdg != NULL; wdg = wdg->next) {
521 wdg_cnt++;
522 if (wdg->busy_pid) {
523 printf("\n[%d] %s\nIdentity: Busy: PID %d (%s)\nDriver: %s\n",
524 wdg_cnt, wdg->dev_node,
525 wdg->busy_pid,
526 wdg->busy_name?wdg->busy_name:"<unknown>",
527 wdg->dev_driver?wdg->dev_driver:"<unknown>");
528 } else {
529 printf("\n[%d] %s\nIdentity: %s\nDriver: %s\n",
530 wdg_cnt, wdg->dev_node,
531 wdg->dev_ident?wdg->dev_ident:
532 "Error: device hogged via alias major/minor?",
533 wdg->dev_driver?wdg->dev_driver:"<unknown>");
534 }
535 if ((wdg->dev_driver) && (strcmp(wdg->dev_driver, "softdog") == 0)) {
536 printf("CAUTION: Not recommended for use with sbd.\n");
537 }
538 }
539
540 return 0;
541 }
542
543 int watchdog_test(void)
544 {
545 int i;
546
547 if ((watchdog_set_timeout == 0) || !watchdog_use) {
548 printf("\nWatchdog is disabled - aborting test!!!\n");
549 return 0;
550 }
551 if (watchdogdev_is_default) {
552 watchdog_populate_list();
553 if (watchdog_list_items > 1) {
554 printf("\nError: Multiple watchdog devices discovered."
555 "\n Use -w <watchdog> or SBD_WATCHDOG_DEV to specify"
556 "\n which device to reset the system with\n");
557 watchdog_info();
558 return -1;
559 }
560 }
561 if ((isatty(fileno(stdin)))) {
562 char buffer[16];
563 printf("\n");
564 printf(
565 "WARNING: This operation is expected to force-reboot this system\n"
566 " without following any shutdown procedures.\n\n"
567 "Proceed? [NO/Proceed] ");
568
569 if ((fgets(buffer, 16, stdin) == NULL) ||
570 strcmp(buffer, "Proceed\n")) {
571 printf("\nAborting watchdog test!!!\n");
572 return 0;
573 }
574 printf("\n");
575 }
576 printf("Initializing %s with a reset countdown of %d seconds ...\n",
577 watchdogdev, (int) timeout_watchdog);
578 if ((watchdog_init() < 0) || (watchdog_init_interval() < 0)) {
579 printf("Failed to initialize watchdog!!!\n");
580 watchdog_info();
581 return -1;
582 }
583 printf("\n");
584 printf(
585 "NOTICE: The watchdog device is expected to reset the system\n"
586 " in %d seconds. If system remains active beyond that time,\n"
587 " watchdog may not be functional.\n\n", timeout_watchdog);
588 for (i=timeout_watchdog; i>1; i--) {
589 printf("Reset countdown ... %d seconds\n", i);
590 sleep(1);
591 }
592 for (i=2; i>0; i--) {
593 printf("System expected to reset any moment ...\n");
594 sleep(1);
595 }
596 for (i=5; i>0; i--) {
597 printf("System should have reset ...\n");
598 sleep(1);
599 }
600 printf("Error: The watchdog device has failed to reboot the system,\n"
601 " and it may not be suitable for usage with sbd.\n");
602
603 /* test should trigger a reboot thus returning is actually bad */
604 return -1;
605 }
606