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