]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * wdctl(8) - show hardware watchdog status | |
3 | * | |
4 | * Copyright (C) 2012 Lennart Poettering | |
5 | * Copyright (C) 2012 Karel Zak <kzak@redhat.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License as published by the | |
9 | * Free Software Foundation; either version 2, or (at your option) any | |
10 | * later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program. If not, see <https://gnu.org/licenses/>. | |
19 | */ | |
20 | #include <sys/ioctl.h> | |
21 | #include <getopt.h> | |
22 | #include <stdio.h> | |
23 | #include <signal.h> | |
24 | #include <assert.h> | |
25 | #include <linux/watchdog.h> | |
26 | #include <sys/types.h> | |
27 | #include <sys/stat.h> | |
28 | #include <unistd.h> | |
29 | ||
30 | #include <libsmartcols.h> | |
31 | ||
32 | #include "nls.h" | |
33 | #include "c.h" | |
34 | #include "cctype.h" | |
35 | #include "xalloc.h" | |
36 | #include "closestream.h" | |
37 | #include "optutils.h" | |
38 | #include "pathnames.h" | |
39 | #include "strutils.h" | |
40 | #include "carefulputc.h" | |
41 | #include "path.h" | |
42 | #include "strv.h" | |
43 | ||
44 | /* | |
45 | * since 2.6.18 | |
46 | */ | |
47 | #ifndef WDIOC_SETPRETIMEOUT | |
48 | # define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int) | |
49 | # define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int) | |
50 | # define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int) | |
51 | # define WDIOF_POWEROVER 0x0040 /* Power over voltage */ | |
52 | # define WDIOF_SETTIMEOUT 0x0080 /* Set timeout (in seconds) */ | |
53 | # define WDIOF_MAGICCLOSE 0x0100 /* Supports magic close char */ | |
54 | # define WDIOF_PRETIMEOUT 0x0200 /* Pretimeout (in seconds), get/set */ | |
55 | # define WDIOF_KEEPALIVEPING 0x8000 /* Keep alive ping reply */ | |
56 | #endif | |
57 | ||
58 | /* | |
59 | * since 3.5 | |
60 | */ | |
61 | #ifndef WDIOF_ALARMONLY | |
62 | # define WDIOF_ALARMONLY 0x0400 /* Watchdog triggers a management or | |
63 | other external alarm not a reboot */ | |
64 | #endif | |
65 | ||
66 | struct wdflag { | |
67 | uint32_t flag; | |
68 | const char *name; | |
69 | const char *description; | |
70 | }; | |
71 | ||
72 | static const struct wdflag wdflags[] = { | |
73 | { WDIOF_CARDRESET, "CARDRESET", N_("Card previously reset the CPU") }, | |
74 | { WDIOF_EXTERN1, "EXTERN1", N_("External relay 1") }, | |
75 | { WDIOF_EXTERN2, "EXTERN2", N_("External relay 2") }, | |
76 | { WDIOF_FANFAULT, "FANFAULT", N_("Fan failed") }, | |
77 | { WDIOF_KEEPALIVEPING, "KEEPALIVEPING", N_("Keep alive ping reply") }, | |
78 | { WDIOF_MAGICCLOSE, "MAGICCLOSE", N_("Supports magic close char") }, | |
79 | { WDIOF_OVERHEAT, "OVERHEAT", N_("Reset due to CPU overheat") }, | |
80 | { WDIOF_POWEROVER, "POWEROVER", N_("Power over voltage") }, | |
81 | { WDIOF_POWERUNDER, "POWERUNDER", N_("Power bad/power fault") }, | |
82 | { WDIOF_PRETIMEOUT, "PRETIMEOUT", N_("Pretimeout (in seconds)") }, | |
83 | { WDIOF_SETTIMEOUT, "SETTIMEOUT", N_("Set timeout (in seconds)") }, | |
84 | { WDIOF_ALARMONLY, "ALARMONLY", N_("Not trigger reboot") } | |
85 | }; | |
86 | ||
87 | ||
88 | /* column names */ | |
89 | struct colinfo { | |
90 | const char * const name; /* header */ | |
91 | double whint; /* width hint (N < 1 is in percent of termwidth) */ | |
92 | int flags; /* SCOLS_FL_* */ | |
93 | const char *help; | |
94 | }; | |
95 | ||
96 | enum { COL_FLAG, COL_DESC, COL_STATUS, COL_BSTATUS, COL_DEVICE }; | |
97 | ||
98 | /* columns descriptions */ | |
99 | static const struct colinfo infos[] = { | |
100 | [COL_FLAG] = { "FLAG", 14, 0, N_("flag name") }, | |
101 | [COL_DESC] = { "DESCRIPTION", 0.1, SCOLS_FL_TRUNC, N_("flag description") }, | |
102 | [COL_STATUS] = { "STATUS", 1, SCOLS_FL_RIGHT, N_("flag status") }, | |
103 | [COL_BSTATUS] = { "BOOT-STATUS", 1, SCOLS_FL_RIGHT, N_("flag boot status") }, | |
104 | [COL_DEVICE] = { "DEVICE", 0.1, 0, N_("watchdog device name") } | |
105 | ||
106 | }; | |
107 | ||
108 | static int columns[ARRAY_SIZE(infos) * 2]; | |
109 | static int ncolumns; | |
110 | ||
111 | struct wd_device { | |
112 | const char *devpath; | |
113 | struct path_cxt *sysfs; | |
114 | ||
115 | char *governor; | |
116 | char **available_governors; | |
117 | ||
118 | int timeout; | |
119 | int timeleft; | |
120 | int pretimeout; | |
121 | ||
122 | uint32_t status; | |
123 | uint32_t bstatus; | |
124 | int nowayout; | |
125 | ||
126 | struct watchdog_info ident; | |
127 | ||
128 | unsigned int has_identity : 1, | |
129 | has_fw_version : 1, | |
130 | has_options : 1, | |
131 | has_status : 1, | |
132 | has_bootstatus : 1, | |
133 | has_timeout : 1, | |
134 | has_timeleft : 1, | |
135 | has_pretimeout : 1, | |
136 | has_nowayout : 1, | |
137 | no_sysfs : 1; | |
138 | }; | |
139 | ||
140 | struct wd_control { | |
141 | /* set */ | |
142 | int timeout; /* --settimeout */ | |
143 | int pretimeout; /* --setpretimeout */ | |
144 | const char *governor; /* --setpregovernor */ | |
145 | unsigned int set_timeout : 1, | |
146 | set_pretimeout : 1; | |
147 | ||
148 | /* output */ | |
149 | unsigned int show_oneline : 1, | |
150 | show_raw : 1, | |
151 | hide_headings : 1, | |
152 | hide_flags : 1, | |
153 | hide_ident : 1, | |
154 | hide_timeouts : 1; | |
155 | }; | |
156 | ||
157 | #define want_set(_ctl) ((_ctl)->set_timeout \ | |
158 | || (_ctl)->set_pretimeout \ | |
159 | || (_ctl)->governor) | |
160 | ||
161 | /* converts flag name to flag bit */ | |
162 | static long name2bit(const char *name, size_t namesz) | |
163 | { | |
164 | size_t i; | |
165 | ||
166 | for (i = 0; i < ARRAY_SIZE(wdflags); i++) { | |
167 | const char *cn = wdflags[i].name; | |
168 | if (!c_strncasecmp(name, cn, namesz) && !*(cn + namesz)) | |
169 | return wdflags[i].flag; | |
170 | } | |
171 | warnx(_("unknown flag: %s"), name); | |
172 | return -1; | |
173 | } | |
174 | ||
175 | static int column2id(const char *name, size_t namesz) | |
176 | { | |
177 | size_t i; | |
178 | ||
179 | for (i = 0; i < ARRAY_SIZE(infos); i++) { | |
180 | const char *cn = infos[i].name; | |
181 | if (!c_strncasecmp(name, cn, namesz) && !*(cn + namesz)) | |
182 | return i; | |
183 | } | |
184 | warnx(_("unknown column: %s"), name); | |
185 | return -1; | |
186 | } | |
187 | ||
188 | static int get_column_id(int num) | |
189 | { | |
190 | assert(num < ncolumns); | |
191 | assert(columns[num] < (int) ARRAY_SIZE(infos)); | |
192 | ||
193 | return columns[num]; | |
194 | } | |
195 | ||
196 | static const struct colinfo *get_column_info(unsigned num) | |
197 | { | |
198 | return &infos[ get_column_id(num) ]; | |
199 | } | |
200 | ||
201 | /* We prefer cdev /dev/watchdog0 as this device has node in | |
202 | * /sys/class/watchdog/. The old miscdev /dev/watchdog is fallback for old | |
203 | * systemds only. | |
204 | */ | |
205 | static const char *get_default_device(void) | |
206 | { | |
207 | const char *const*p; | |
208 | static const char *const devs[] = { | |
209 | "/dev/watchdog0", | |
210 | "/dev/watchdog", | |
211 | NULL | |
212 | }; | |
213 | ||
214 | for (p = devs; *p; p++) { | |
215 | if (access(*p, F_OK) == 0) | |
216 | return *p; | |
217 | } | |
218 | ||
219 | return NULL; | |
220 | } | |
221 | ||
222 | static void __attribute__((__noreturn__)) usage(void) | |
223 | { | |
224 | FILE *out = stdout; | |
225 | size_t i; | |
226 | const char *dflt = get_default_device(); | |
227 | ||
228 | fputs(USAGE_HEADER, out); | |
229 | fprintf(out, | |
230 | _(" %s [options] [<device> ...]\n"), program_invocation_short_name); | |
231 | ||
232 | fputs(USAGE_SEPARATOR, out); | |
233 | fputs(_("Show the status of the hardware watchdog.\n"), out); | |
234 | ||
235 | fputs(USAGE_OPTIONS, out); | |
236 | fputs(_(" -f, --flags <list> print selected flags only\n" | |
237 | " -F, --noflags don't print information about flags\n" | |
238 | " -I, --noident don't print watchdog identity information\n" | |
239 | " -n, --noheadings don't print headings for flags table\n" | |
240 | " -O, --oneline print all information on one line\n" | |
241 | " -o, --output <list> output columns of the flags\n" | |
242 | " -p, --setpretimeout <sec> set watchdog pre-timeout\n" | |
243 | " -g, --setpregovernor <name> set pre-timeout governor\n" | |
244 | " -r, --raw use raw output format for flags table\n" | |
245 | " -T, --notimeouts don't print watchdog timeouts\n" | |
246 | " -s, --settimeout <sec> set watchdog timeout\n" | |
247 | " -x, --flags-only print only flags table (same as -I -T)\n"), out); | |
248 | ||
249 | fputs(USAGE_SEPARATOR, out); | |
250 | fprintf(out, USAGE_HELP_OPTIONS(24)); | |
251 | fputs(USAGE_SEPARATOR, out); | |
252 | ||
253 | if (dflt) | |
254 | fprintf(out, _("The default device is %s.\n"), dflt); | |
255 | else | |
256 | fprintf(out, _("No default device is available.\n")); | |
257 | ||
258 | fputs(USAGE_COLUMNS, out); | |
259 | for (i = 0; i < ARRAY_SIZE(infos); i++) | |
260 | fprintf(out, " %13s %s\n", infos[i].name, _(infos[i].help)); | |
261 | ||
262 | fprintf(out, USAGE_MAN_TAIL("wdctl(8)")); | |
263 | ||
264 | exit(EXIT_SUCCESS); | |
265 | } | |
266 | ||
267 | static struct path_cxt *get_sysfs(struct wd_device *wd) | |
268 | { | |
269 | struct path_cxt *sys; | |
270 | struct stat st; | |
271 | ||
272 | if (wd->no_sysfs) | |
273 | return NULL; | |
274 | if (wd->sysfs) | |
275 | return wd->sysfs; | |
276 | if (stat(wd->devpath, &st) != 0) | |
277 | goto nosysfs; | |
278 | ||
279 | sys = ul_new_path(_PATH_SYS_DEVCHAR "/%u:%u", | |
280 | major(st.st_rdev), minor(st.st_rdev)); | |
281 | if (!sys) | |
282 | return NULL; | |
283 | ||
284 | if (ul_path_get_dirfd(sys) < 0) | |
285 | goto nosysfs; /* device not in /sys */ | |
286 | ||
287 | if (ul_path_access(sys, F_OK, "identity") != 0) | |
288 | goto nosysfs; /* no info in /sys (old miscdev?) */ | |
289 | ||
290 | wd->sysfs = sys; | |
291 | return sys; | |
292 | nosysfs: | |
293 | wd->no_sysfs = 1; | |
294 | return NULL; | |
295 | } | |
296 | ||
297 | static void add_flag_line(struct libscols_table *table, struct wd_device *wd, const struct wdflag *fl) | |
298 | { | |
299 | int i; | |
300 | struct libscols_line *line; | |
301 | ||
302 | line = scols_table_new_line(table, NULL); | |
303 | if (!line) { | |
304 | warn(_("failed to allocate output line")); | |
305 | return; | |
306 | } | |
307 | ||
308 | for (i = 0; i < ncolumns; i++) { | |
309 | const char *str = NULL; | |
310 | ||
311 | switch (get_column_id(i)) { | |
312 | case COL_FLAG: | |
313 | str = fl->name; | |
314 | break; | |
315 | case COL_DESC: | |
316 | str = fl->description; | |
317 | break; | |
318 | case COL_STATUS: | |
319 | str = wd->status & fl->flag ? "1" : "0"; | |
320 | break; | |
321 | case COL_BSTATUS: | |
322 | str = wd->bstatus & fl->flag ? "1" : "0"; | |
323 | break; | |
324 | case COL_DEVICE: | |
325 | str = wd->devpath; | |
326 | break; | |
327 | default: | |
328 | break; | |
329 | } | |
330 | ||
331 | if (str && scols_line_set_data(line, i, str)) { | |
332 | warn(_("failed to add output data")); | |
333 | break; | |
334 | } | |
335 | } | |
336 | } | |
337 | ||
338 | static int show_flags(struct wd_control *ctl, struct wd_device *wd, uint32_t wanted) | |
339 | { | |
340 | size_t i; | |
341 | int rc = -1; | |
342 | struct libscols_table *table; | |
343 | uint32_t flags; | |
344 | ||
345 | /* information about supported bits is probably missing in /sys */ | |
346 | if (!wd->ident.options) | |
347 | return 0; | |
348 | ||
349 | scols_init_debug(0); | |
350 | ||
351 | /* create output table */ | |
352 | table = scols_new_table(); | |
353 | if (!table) { | |
354 | warn(_("failed to allocate output table")); | |
355 | return -1; | |
356 | } | |
357 | scols_table_enable_raw(table, ctl->show_raw); | |
358 | scols_table_enable_noheadings(table, ctl->hide_headings); | |
359 | ||
360 | /* define columns */ | |
361 | for (i = 0; i < (size_t) ncolumns; i++) { | |
362 | const struct colinfo *col = get_column_info(i); | |
363 | ||
364 | if (!scols_table_new_column(table, col->name, col->whint, col->flags)) { | |
365 | warnx(_("failed to allocate output column")); | |
366 | goto done; | |
367 | } | |
368 | } | |
369 | ||
370 | /* fill-in table with data | |
371 | * -- one line for each supported flag (option) */ | |
372 | flags = wd->ident.options; | |
373 | ||
374 | for (i = 0; i < ARRAY_SIZE(wdflags); i++) { | |
375 | if (wanted && !(wanted & wdflags[i].flag)) | |
376 | ; /* ignore */ | |
377 | else if (flags & wdflags[i].flag) | |
378 | add_flag_line(table, wd, &wdflags[i]); | |
379 | ||
380 | flags &= ~wdflags[i].flag; | |
381 | } | |
382 | ||
383 | if (flags) | |
384 | warnx(_("%s: unknown flags 0x%x\n"), wd->devpath, flags); | |
385 | ||
386 | scols_print_table(table); | |
387 | rc = 0; | |
388 | done: | |
389 | scols_unref_table(table); | |
390 | return rc; | |
391 | } | |
392 | ||
393 | /* | |
394 | * Warning: successfully opened watchdog has to be properly closed with magic | |
395 | * close character otherwise the machine will be rebooted! | |
396 | * | |
397 | * Don't use err() or exit() here! | |
398 | */ | |
399 | static int set_watchdog(struct wd_control *ctl, struct wd_device *wd) | |
400 | { | |
401 | int fd; | |
402 | sigset_t sigs, oldsigs; | |
403 | int rc = 0; | |
404 | ||
405 | assert(wd); | |
406 | assert(wd->devpath); | |
407 | assert(ctl); | |
408 | ||
409 | if (!ctl->set_timeout && !ctl->set_pretimeout) | |
410 | goto sysfs_only; | |
411 | ||
412 | sigemptyset(&oldsigs); | |
413 | sigfillset(&sigs); | |
414 | sigprocmask(SIG_BLOCK, &sigs, &oldsigs); | |
415 | ||
416 | fd = open(wd->devpath, O_WRONLY|O_CLOEXEC); | |
417 | ||
418 | if (fd < 0) { | |
419 | if (errno == EBUSY) | |
420 | warnx(_("%s: watchdog already in use, terminating."), | |
421 | wd->devpath); | |
422 | warn(_("cannot open %s"), wd->devpath); | |
423 | return -1; | |
424 | } | |
425 | ||
426 | for (;;) { | |
427 | /* We just opened this to query the state, not to arm | |
428 | * it hence use the magic close character */ | |
429 | static const char v = 'V'; | |
430 | ||
431 | if (write(fd, &v, 1) >= 0) | |
432 | break; | |
433 | if (errno != EINTR) { | |
434 | warn(_("%s: failed to disarm watchdog"), wd->devpath); | |
435 | break; | |
436 | } | |
437 | /* Let's try hard, since if we don't get this right | |
438 | * the machine might end up rebooting. */ | |
439 | } | |
440 | ||
441 | if (ctl->set_timeout) { | |
442 | if (ioctl(fd, WDIOC_SETTIMEOUT, &ctl->timeout) != 0) { | |
443 | rc += errno; | |
444 | warn(_("cannot set timeout for %s"), wd->devpath); | |
445 | } else | |
446 | printf(P_("Timeout has been set to %d second.\n", | |
447 | "Timeout has been set to %d seconds.\n", | |
448 | ctl->timeout), ctl->timeout); | |
449 | } | |
450 | ||
451 | if (ctl->set_pretimeout) { | |
452 | if (ioctl(fd, WDIOC_SETPRETIMEOUT, &ctl->pretimeout) != 0) { | |
453 | rc += errno; | |
454 | warn(_("cannot set pretimeout for %s"), wd->devpath); | |
455 | } else | |
456 | printf(P_("Pre-timeout has been set to %d second.\n", | |
457 | "Pre-timeout has been set to %d seconds.\n", | |
458 | ctl->pretimeout), ctl->pretimeout); | |
459 | } | |
460 | ||
461 | if (close(fd)) | |
462 | warn(_("write failed")); | |
463 | ||
464 | sigprocmask(SIG_SETMASK, &oldsigs, NULL); | |
465 | ||
466 | sysfs_only: | |
467 | if (ctl->governor) { | |
468 | struct path_cxt *sys = get_sysfs(wd); | |
469 | int xrc; | |
470 | ||
471 | xrc = !sys ? errno : | |
472 | ul_path_write_string(sys, ctl->governor, | |
473 | "pretimeout_governor"); | |
474 | if (xrc) | |
475 | warn(_("cannot set pre-timeout governor")); | |
476 | rc += xrc; | |
477 | } | |
478 | ||
479 | return rc; | |
480 | } | |
481 | ||
482 | /* | |
483 | * Warning: successfully opened watchdog has to be properly closed with magic | |
484 | * close character otherwise the machine will be rebooted! | |
485 | * | |
486 | * Don't use err() or exit() here! | |
487 | */ | |
488 | static int read_watchdog_from_device(struct wd_device *wd) | |
489 | { | |
490 | int fd; | |
491 | sigset_t sigs, oldsigs; | |
492 | ||
493 | assert(wd->devpath); | |
494 | ||
495 | sigemptyset(&oldsigs); | |
496 | sigfillset(&sigs); | |
497 | sigprocmask(SIG_BLOCK, &sigs, &oldsigs); | |
498 | ||
499 | fd = open(wd->devpath, O_WRONLY|O_CLOEXEC); | |
500 | ||
501 | if (fd < 0) | |
502 | return -errno; | |
503 | ||
504 | if (ioctl(fd, WDIOC_GETSUPPORT, &wd->ident) < 0) | |
505 | warn(_("%s: failed to get information about watchdog"), wd->devpath); | |
506 | else { | |
507 | ioctl(fd, WDIOC_GETSTATUS, &wd->status); | |
508 | ioctl(fd, WDIOC_GETBOOTSTATUS, &wd->bstatus); | |
509 | ||
510 | /* | |
511 | * Sometimes supported options like WDIOF_CARDRESET are missing from | |
512 | * ident.options, add anything set in status/bstatus to ident.options. | |
513 | */ | |
514 | wd->ident.options |= wd->status; | |
515 | wd->ident.options |= wd->bstatus; | |
516 | ||
517 | if (ioctl(fd, WDIOC_GETTIMEOUT, &wd->timeout) >= 0) | |
518 | wd->has_timeout = 1; | |
519 | if (ioctl(fd, WDIOC_GETPRETIMEOUT, &wd->pretimeout) >= 0) | |
520 | wd->has_pretimeout = 1; | |
521 | if (ioctl(fd, WDIOC_GETTIMELEFT, &wd->timeleft) >= 0) | |
522 | wd->has_timeleft = 1; | |
523 | } | |
524 | ||
525 | for (;;) { | |
526 | /* We just opened this to query the state, not to arm | |
527 | * it hence use the magic close character */ | |
528 | static const char v = 'V'; | |
529 | ||
530 | if (write(fd, &v, 1) >= 0) | |
531 | break; | |
532 | if (errno != EINTR) { | |
533 | warn(_("%s: failed to disarm watchdog"), wd->devpath); | |
534 | break; | |
535 | } | |
536 | /* Let's try hard, since if we don't get this right | |
537 | * the machine might end up rebooting. */ | |
538 | } | |
539 | ||
540 | if (close(fd)) | |
541 | warn(_("write failed")); | |
542 | sigprocmask(SIG_SETMASK, &oldsigs, NULL); | |
543 | ||
544 | return 0; | |
545 | } | |
546 | ||
547 | ||
548 | /* Returns: <0 on error, 0 on success, 1 for unsupported */ | |
549 | static int read_watchdog_from_sysfs(struct wd_device *wd) | |
550 | { | |
551 | struct path_cxt *sys; | |
552 | ||
553 | sys = get_sysfs(wd); | |
554 | if (!sys) | |
555 | return 1; | |
556 | ||
557 | if (ul_path_read_buffer(sys, (char *) wd->ident.identity, sizeof(wd->ident.identity), "identity") >= 0) | |
558 | wd->has_identity = 1; | |
559 | if (ul_path_read_u32(sys, &wd->ident.firmware_version, "fw_version") == 0) | |
560 | wd->has_fw_version = 1; | |
561 | if (ul_path_scanf(sys, "options", "%x", &wd->ident.options) == 1) | |
562 | wd->has_options = 1; | |
563 | if (ul_path_scanf(sys, "status", "%x", &wd->status) == 1) | |
564 | wd->has_status = 1; | |
565 | if (ul_path_read_u32(sys, &wd->bstatus, "bootstatus") == 0) | |
566 | wd->has_bootstatus = 1; | |
567 | if (ul_path_read_s32(sys, &wd->nowayout, "nowayout") == 0) | |
568 | wd->has_nowayout = 1; | |
569 | if (ul_path_read_s32(sys, &wd->timeout, "timeout") == 0) | |
570 | wd->has_timeout = 1; | |
571 | if (ul_path_read_s32(sys, &wd->pretimeout, "pretimeout") == 0) | |
572 | wd->has_pretimeout = 1; | |
573 | if (ul_path_read_s32(sys, &wd->timeleft, "timeleft") == 0) | |
574 | wd->has_timeleft = 1; | |
575 | ||
576 | return 0; | |
577 | } | |
578 | ||
579 | static int read_governors(struct wd_device *wd) | |
580 | { | |
581 | struct path_cxt *sys; | |
582 | FILE *f; | |
583 | ||
584 | sys = get_sysfs(wd); | |
585 | if (!sys) | |
586 | return 1; | |
587 | ||
588 | f = ul_path_fopen(sys, "r", "pretimeout_available_governors"); | |
589 | if (f) { | |
590 | char *line = NULL; | |
591 | size_t dummy = 0; | |
592 | ssize_t sz; | |
593 | ||
594 | while ((sz = getline(&line, &dummy, f)) >= 0) { | |
595 | if (rtrim_whitespace((unsigned char *) line) == 0) | |
596 | continue; | |
597 | ul_strv_consume(&wd->available_governors, line); | |
598 | dummy = 0; | |
599 | line = NULL; | |
600 | } | |
601 | free(line); | |
602 | fclose(f); | |
603 | } | |
604 | ||
605 | ul_path_read_string(sys, &wd->governor, "pretimeout_governor"); | |
606 | return 0; | |
607 | } | |
608 | ||
609 | static bool should_read_from_device(struct wd_device *wd) | |
610 | { | |
611 | if (wd->no_sysfs) | |
612 | return true; | |
613 | ||
614 | if (!wd->has_nowayout) | |
615 | return false; | |
616 | ||
617 | if (wd->nowayout) | |
618 | return false; | |
619 | ||
620 | return !wd->has_identity || | |
621 | !wd->has_fw_version || | |
622 | !wd->has_options || | |
623 | !wd->has_status || | |
624 | !wd->has_bootstatus || | |
625 | !wd->has_timeout || | |
626 | !wd->has_timeleft; | |
627 | // pretimeout attribute may be hidden in sysfs | |
628 | } | |
629 | ||
630 | static int read_watchdog(struct wd_device *wd) | |
631 | { | |
632 | int rc; | |
633 | ||
634 | rc = read_watchdog_from_sysfs(wd); | |
635 | ||
636 | if (rc && should_read_from_device(wd)) | |
637 | rc = read_watchdog_from_device(wd); | |
638 | ||
639 | if (rc) { | |
640 | warn(_("cannot read information about %s"), wd->devpath); | |
641 | return -1; | |
642 | } | |
643 | ||
644 | read_governors(wd); | |
645 | return 0; | |
646 | } | |
647 | ||
648 | static void show_timeouts(struct wd_device *wd) | |
649 | { | |
650 | if (wd->has_timeout) | |
651 | printf(P_("%-14s %2i second\n", "%-14s %2i seconds\n", wd->timeout), | |
652 | _("Timeout:"), wd->timeout); | |
653 | if (wd->has_timeleft) | |
654 | printf(P_("%-14s %2i second\n", "%-14s %2i seconds\n", wd->timeleft), | |
655 | _("Timeleft:"), wd->timeleft); | |
656 | if (wd->has_pretimeout) | |
657 | printf(P_("%-14s %2i second\n", "%-14s %2i seconds\n", wd->pretimeout), | |
658 | _("Pre-timeout:"), wd->pretimeout); | |
659 | } | |
660 | ||
661 | static void show_governors(struct wd_device *wd) | |
662 | { | |
663 | if (wd->governor) | |
664 | printf(_("%-14s %s\n"), _("Pre-timeout governor:"), wd->governor); | |
665 | if (wd->available_governors) { | |
666 | char *tmp = ul_strv_join(wd->available_governors, " "); | |
667 | ||
668 | if (tmp) | |
669 | printf(_("%-14s %s\n"), | |
670 | _("Available pre-timeout governors:"), tmp); | |
671 | free(tmp); | |
672 | } | |
673 | } | |
674 | ||
675 | static void print_oneline(struct wd_control *ctl, struct wd_device *wd, uint32_t wanted) | |
676 | { | |
677 | printf("%s:", wd->devpath); | |
678 | ||
679 | if (!ctl->hide_ident) { | |
680 | printf(" VERSION=\"%x\"", wd->ident.firmware_version); | |
681 | ||
682 | printf(" IDENTITY="); | |
683 | fputs_quoted((char *) wd->ident.identity, stdout); | |
684 | } | |
685 | if (!ctl->hide_timeouts) { | |
686 | if (wd->has_timeout) | |
687 | printf(" TIMEOUT=\"%i\"", wd->timeout); | |
688 | if (wd->has_pretimeout) | |
689 | printf(" PRETIMEOUT=\"%i\"", wd->pretimeout); | |
690 | if (wd->has_timeleft) | |
691 | printf(" TIMELEFT=\"%i\"", wd->timeleft); | |
692 | } | |
693 | ||
694 | if (!ctl->hide_flags) { | |
695 | size_t i; | |
696 | uint32_t flags = wd->ident.options; | |
697 | ||
698 | for (i = 0; i < ARRAY_SIZE(wdflags); i++) { | |
699 | const struct wdflag *fl; | |
700 | ||
701 | if ((wanted && !(wanted & wdflags[i].flag)) || | |
702 | !(flags & wdflags[i].flag)) | |
703 | continue; | |
704 | ||
705 | fl= &wdflags[i]; | |
706 | ||
707 | printf(" %s=\"%s\"", fl->name, | |
708 | wd->status & fl->flag ? "1" : "0"); | |
709 | printf(" %s_BOOT=\"%s\"", fl->name, | |
710 | wd->bstatus & fl->flag ? "1" : "0"); | |
711 | ||
712 | } | |
713 | } | |
714 | ||
715 | fputc('\n', stdout); | |
716 | } | |
717 | ||
718 | static void print_device(struct wd_control *ctl, struct wd_device *wd, uint32_t wanted) | |
719 | { | |
720 | /* NAME=value one line output */ | |
721 | if (ctl->show_oneline) { | |
722 | print_oneline(ctl, wd, wanted); | |
723 | return; | |
724 | } | |
725 | ||
726 | /* pretty output */ | |
727 | if (!ctl->hide_ident) { | |
728 | printf("%-15s%s\n", _("Device:"), wd->devpath); | |
729 | printf("%-15s%s [%s %x]\n", | |
730 | _("Identity:"), | |
731 | wd->ident.identity, | |
732 | _("version"), | |
733 | wd->ident.firmware_version); | |
734 | } | |
735 | if (!ctl->hide_timeouts) | |
736 | show_timeouts(wd); | |
737 | ||
738 | show_governors(wd); | |
739 | ||
740 | if (!ctl->hide_flags) | |
741 | show_flags(ctl, wd, wanted); | |
742 | } | |
743 | ||
744 | int main(int argc, char *argv[]) | |
745 | { | |
746 | struct wd_device wd; | |
747 | struct wd_control ctl = { .hide_headings = 0 }; | |
748 | int c, res = EXIT_SUCCESS, count = 0; | |
749 | unsigned long wanted = 0; | |
750 | const char *dflt_device = NULL; | |
751 | ||
752 | static const struct option long_opts[] = { | |
753 | { "flags", required_argument, NULL, 'f' }, | |
754 | { "flags-only", no_argument, NULL, 'x' }, | |
755 | { "help", no_argument, NULL, 'h' }, | |
756 | { "noflags", no_argument, NULL, 'F' }, | |
757 | { "noheadings", no_argument, NULL, 'n' }, | |
758 | { "noident", no_argument, NULL, 'I' }, | |
759 | { "notimeouts", no_argument, NULL, 'T' }, | |
760 | { "settimeout", required_argument, NULL, 's' }, | |
761 | { "setpretimeout", required_argument, NULL, 'p' }, | |
762 | { "setpregovernor", required_argument, NULL, 'g' }, | |
763 | { "output", required_argument, NULL, 'o' }, | |
764 | { "oneline", no_argument, NULL, 'O' }, | |
765 | { "raw", no_argument, NULL, 'r' }, | |
766 | { "version", no_argument, NULL, 'V' }, | |
767 | { NULL, 0, NULL, 0 } | |
768 | }; | |
769 | ||
770 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ | |
771 | { 'F','f' }, /* noflags,flags*/ | |
772 | { 0 } | |
773 | }; | |
774 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
775 | ||
776 | setlocale(LC_ALL, ""); | |
777 | bindtextdomain(PACKAGE, LOCALEDIR); | |
778 | textdomain(PACKAGE); | |
779 | close_stdout_atexit(); | |
780 | ||
781 | while ((c = getopt_long(argc, argv, | |
782 | "d:f:g:hFnITp:o:s:OrVx", long_opts, NULL)) != -1) { | |
783 | ||
784 | err_exclusive_options(c, long_opts, excl, excl_st); | |
785 | ||
786 | switch(c) { | |
787 | case 'o': | |
788 | ncolumns = string_to_idarray(optarg, | |
789 | columns, ARRAY_SIZE(columns), | |
790 | column2id); | |
791 | if (ncolumns < 0) | |
792 | return EXIT_FAILURE; | |
793 | break; | |
794 | case 's': | |
795 | ctl.timeout = strtos32_or_err(optarg, _("invalid timeout")); | |
796 | ctl.set_timeout = 1; | |
797 | break; | |
798 | case 'p': | |
799 | ctl.pretimeout = strtos32_or_err(optarg, _("invalid pretimeout")); | |
800 | ctl.set_pretimeout = 1; | |
801 | break; | |
802 | case 'f': | |
803 | if (string_to_bitmask(optarg, &wanted, name2bit) != 0) | |
804 | return EXIT_FAILURE; | |
805 | break; | |
806 | case 'F': | |
807 | ctl.hide_flags = 1; | |
808 | break; | |
809 | case 'g': | |
810 | ctl.governor = optarg; | |
811 | break; | |
812 | case 'I': | |
813 | ctl.hide_ident = 1; | |
814 | break; | |
815 | case 'T': | |
816 | ctl.hide_timeouts = 1; | |
817 | break; | |
818 | case 'n': | |
819 | ctl.hide_headings = 1; | |
820 | break; | |
821 | case 'r': | |
822 | ctl.show_raw = 1; | |
823 | break; | |
824 | case 'O': | |
825 | ctl.show_oneline = 1; | |
826 | break; | |
827 | case 'x': | |
828 | ctl.hide_ident = 1; | |
829 | ctl.hide_timeouts = 1; | |
830 | break; | |
831 | ||
832 | case 'h': | |
833 | usage(); | |
834 | case 'V': | |
835 | print_version(EXIT_SUCCESS); | |
836 | default: | |
837 | errtryhelp(EXIT_FAILURE); | |
838 | } | |
839 | } | |
840 | ||
841 | if (!ncolumns) { | |
842 | /* default columns */ | |
843 | columns[ncolumns++] = COL_FLAG; | |
844 | columns[ncolumns++] = COL_DESC; | |
845 | columns[ncolumns++] = COL_STATUS; | |
846 | columns[ncolumns++] = COL_BSTATUS; | |
847 | } | |
848 | ||
849 | /* Device no specified, use default. */ | |
850 | if (optind == argc) { | |
851 | dflt_device = get_default_device(); | |
852 | if (!dflt_device) | |
853 | err(EXIT_FAILURE, _("No default device is available.")); | |
854 | } | |
855 | ||
856 | do { | |
857 | int rc; | |
858 | ||
859 | memset(&wd, 0, sizeof(wd)); | |
860 | wd.devpath = dflt_device ? dflt_device : argv[optind++]; | |
861 | ||
862 | if (count) | |
863 | fputc('\n', stdout); | |
864 | count++; | |
865 | ||
866 | if (want_set(&ctl)) { | |
867 | rc = set_watchdog(&ctl, &wd); | |
868 | if (rc) { | |
869 | res = EXIT_FAILURE; | |
870 | } | |
871 | } | |
872 | ||
873 | rc = read_watchdog(&wd); | |
874 | if (rc) { | |
875 | res = EXIT_FAILURE; | |
876 | continue; | |
877 | } | |
878 | ||
879 | print_device(&ctl, &wd, wanted); | |
880 | ul_unref_path(wd.sysfs); | |
881 | } while (optind < argc); | |
882 | ||
883 | return res; | |
884 | } |