]>
Commit | Line | Data |
---|---|---|
8fb6a2de LP |
1 | /* |
2 | * wdctl(8) - show hardware watchdog status | |
3 | * | |
4 | * Copyright (C) 2012 Lennart Poettering | |
09f9a393 | 5 | * Copyright (C) 2012 Karel Zak <kzak@redhat.com> |
8fb6a2de LP |
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 along | |
18 | * with this program; if not, write to the Free Software Foundation, Inc., | |
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
20 | */ | |
8fb6a2de LP |
21 | #include <sys/ioctl.h> |
22 | #include <getopt.h> | |
23 | #include <stdio.h> | |
09f9a393 KZ |
24 | #include <signal.h> |
25 | #include <assert.h> | |
7560aebe | 26 | #include <linux/watchdog.h> |
b3dd29d1 KZ |
27 | #include <sys/types.h> |
28 | #include <sys/stat.h> | |
29 | #include <unistd.h> | |
8fb6a2de | 30 | |
fe7af530 OO |
31 | #include <libsmartcols.h> |
32 | ||
8fb6a2de LP |
33 | #include "nls.h" |
34 | #include "c.h" | |
2ffddb6a | 35 | #include "xalloc.h" |
33a0de92 | 36 | #include "closestream.h" |
e6dbcc4a | 37 | #include "optutils.h" |
33a0de92 | 38 | #include "pathnames.h" |
09f9a393 | 39 | #include "strutils.h" |
fe7af530 | 40 | #include "carefulputc.h" |
b3dd29d1 | 41 | #include "path.h" |
86ffb5a7 | 42 | #include "strv.h" |
8fb6a2de | 43 | |
2eb5ba0b KZ |
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 | ||
09f9a393 KZ |
66 | struct wdflag { |
67 | uint32_t flag; | |
68 | const char *name; | |
69 | const char *description; | |
8fb6a2de LP |
70 | }; |
71 | ||
09f9a393 KZ |
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)") }, | |
2eb5ba0b KZ |
83 | { WDIOF_SETTIMEOUT, "SETTIMEOUT", N_("Set timeout (in seconds)") }, |
84 | { WDIOF_ALARMONLY, "ALARMONLY", N_("Not trigger reboot") } | |
09f9a393 KZ |
85 | }; |
86 | ||
87 | ||
88 | /* column names */ | |
89 | struct colinfo { | |
37b2b3fa TW |
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; | |
09f9a393 KZ |
94 | }; |
95 | ||
96747e75 | 96 | enum { COL_FLAG, COL_DESC, COL_STATUS, COL_BSTATUS, COL_DEVICE }; |
09f9a393 KZ |
97 | |
98 | /* columns descriptions */ | |
37b2b3fa | 99 | static const struct colinfo infos[] = { |
09f9a393 | 100 | [COL_FLAG] = { "FLAG", 14, 0, N_("flag name") }, |
fe7af530 OO |
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") }, | |
96747e75 KZ |
104 | [COL_DEVICE] = { "DEVICE", 0.1, 0, N_("watchdog device name") } |
105 | ||
09f9a393 KZ |
106 | }; |
107 | ||
059a91f8 KZ |
108 | static int columns[ARRAY_SIZE(infos) * 2]; |
109 | static int ncolumns; | |
09f9a393 | 110 | |
e4d511d4 | 111 | struct wd_device { |
8c8df421 | 112 | const char *devpath; |
45303291 | 113 | struct path_cxt *sysfs; |
09f9a393 | 114 | |
86ffb5a7 KZ |
115 | char *governor; |
116 | char **available_governors; | |
117 | ||
09f9a393 KZ |
118 | int timeout; |
119 | int timeleft; | |
120 | int pretimeout; | |
121 | ||
122 | uint32_t status; | |
123 | uint32_t bstatus; | |
b3dd29d1 | 124 | int nowayout; |
09f9a393 KZ |
125 | |
126 | struct watchdog_info ident; | |
127 | ||
6de3def8 TW |
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, | |
09f9a393 | 134 | has_timeleft : 1, |
b3dd29d1 | 135 | has_pretimeout : 1, |
45303291 KZ |
136 | has_nowayout : 1, |
137 | no_sysfs : 1; | |
09f9a393 KZ |
138 | }; |
139 | ||
a599d137 | 140 | struct wd_control { |
7d64a2b5 KZ |
141 | /* set */ |
142 | int timeout; /* --settimeout */ | |
143 | int pretimeout; /* --setpretimeout */ | |
c0762da1 | 144 | const char *governor; /* --setpregovernor */ |
7d64a2b5 KZ |
145 | unsigned int set_timeout : 1, |
146 | set_pretimeout : 1; | |
147 | ||
148 | /* output */ | |
a599d137 KZ |
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 | ||
c0762da1 KZ |
157 | #define want_set(_ctl) ((_ctl)->set_timeout \ |
158 | || (_ctl)->set_pretimeout \ | |
159 | || (_ctl)->governor) | |
7d64a2b5 | 160 | |
09f9a393 KZ |
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 (!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 | ||
059a91f8 | 179 | for (i = 0; i < ARRAY_SIZE(infos); i++) { |
09f9a393 KZ |
180 | const char *cn = infos[i].name; |
181 | if (!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 | { | |
09f9a393 | 190 | assert(num < ncolumns); |
059a91f8 | 191 | assert(columns[num] < (int) ARRAY_SIZE(infos)); |
09f9a393 KZ |
192 | |
193 | return columns[num]; | |
194 | } | |
195 | ||
37b2b3fa | 196 | static const struct colinfo *get_column_info(unsigned num) |
09f9a393 KZ |
197 | { |
198 | return &infos[ get_column_id(num) ]; | |
199 | } | |
200 | ||
8c8df421 KZ |
201 | /* We preffer 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 **p; | |
208 | static const char *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 | ||
86be6a32 | 222 | static void __attribute__((__noreturn__)) usage(void) |
8fb6a2de | 223 | { |
86be6a32 | 224 | FILE *out = stdout; |
09f9a393 | 225 | size_t i; |
8c8df421 | 226 | const char *dflt = get_default_device(); |
8fb6a2de LP |
227 | |
228 | fputs(USAGE_HEADER, out); | |
229 | fprintf(out, | |
f56338b4 | 230 | _(" %s [options] [<device> ...]\n"), program_invocation_short_name); |
8fb6a2de | 231 | |
451dbcfa BS |
232 | fputs(USAGE_SEPARATOR, out); |
233 | fputs(_("Show the status of the hardware watchdog.\n"), out); | |
8fb6a2de | 234 | |
451dbcfa | 235 | fputs(USAGE_OPTIONS, out); |
fb8b62df HH |
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" | |
152b47d2 | 242 | " -p, --setpretimeout <sec> set watchdog pre-timeout\n" |
c0762da1 | 243 | " -g, --setpregovernor <name> set pre-timeout governor\n" |
fb8b62df HH |
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); | |
09f9a393 | 248 | |
8fb6a2de | 249 | fputs(USAGE_SEPARATOR, out); |
bad4c729 | 250 | fprintf(out, USAGE_HELP_OPTIONS(24)); |
09f9a393 KZ |
251 | fputs(USAGE_SEPARATOR, out); |
252 | ||
8c8df421 KZ |
253 | if (dflt) |
254 | fprintf(out, _("The default device is %s.\n"), dflt); | |
255 | else | |
256 | fprintf(out, _("No default device is available.\n")); | |
f56338b4 | 257 | |
6e2d5a44 | 258 | fputs(USAGE_COLUMNS, out); |
09f9a393 KZ |
259 | for (i = 0; i < ARRAY_SIZE(infos); i++) |
260 | fprintf(out, " %13s %s\n", infos[i].name, _(infos[i].help)); | |
261 | ||
bad4c729 | 262 | fprintf(out, USAGE_MAN_TAIL("wdctl(8)")); |
8fb6a2de | 263 | |
86be6a32 | 264 | exit(EXIT_SUCCESS); |
8fb6a2de LP |
265 | } |
266 | ||
7d64a2b5 KZ |
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 | ||
e4d511d4 | 297 | static void add_flag_line(struct libscols_table *table, struct wd_device *wd, const struct wdflag *fl) |
8fb6a2de | 298 | { |
09f9a393 | 299 | int i; |
fe7af530 | 300 | struct libscols_line *line; |
8fb6a2de | 301 | |
fe7af530 | 302 | line = scols_table_new_line(table, NULL); |
09f9a393 | 303 | if (!line) { |
780ce22c | 304 | warn(_("failed to allocate output line")); |
8fb6a2de LP |
305 | return; |
306 | } | |
307 | ||
09f9a393 KZ |
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; | |
96747e75 | 324 | case COL_DEVICE: |
e4d511d4 | 325 | str = wd->devpath; |
96747e75 | 326 | break; |
09f9a393 KZ |
327 | default: |
328 | break; | |
329 | } | |
8fb6a2de | 330 | |
780ce22c KZ |
331 | if (str && scols_line_set_data(line, i, str)) { |
332 | warn(_("failed to add output data")); | |
333 | break; | |
334 | } | |
09f9a393 | 335 | } |
8fb6a2de LP |
336 | } |
337 | ||
a599d137 | 338 | static int show_flags(struct wd_control *ctl, struct wd_device *wd, uint32_t wanted) |
8fb6a2de | 339 | { |
09f9a393 KZ |
340 | size_t i; |
341 | int rc = -1; | |
fe7af530 | 342 | struct libscols_table *table; |
09f9a393 KZ |
343 | uint32_t flags; |
344 | ||
b3dd29d1 KZ |
345 | /* information about supported bits is probably missing in /sys */ |
346 | if (!wd->ident.options) | |
347 | return 0; | |
348 | ||
710ed55d KZ |
349 | scols_init_debug(0); |
350 | ||
09f9a393 | 351 | /* create output table */ |
0925a9dd | 352 | table = scols_new_table(); |
fe7af530 | 353 | if (!table) { |
780ce22c | 354 | warn(_("failed to allocate output table")); |
09f9a393 KZ |
355 | return -1; |
356 | } | |
a599d137 KZ |
357 | scols_table_enable_raw(table, ctl->show_raw); |
358 | scols_table_enable_noheadings(table, ctl->hide_headings); | |
8fb6a2de | 359 | |
09f9a393 KZ |
360 | /* define columns */ |
361 | for (i = 0; i < (size_t) ncolumns; i++) { | |
37b2b3fa | 362 | const struct colinfo *col = get_column_info(i); |
8fb6a2de | 363 | |
fe7af530 | 364 | if (!scols_table_new_column(table, col->name, col->whint, col->flags)) { |
780ce22c | 365 | warnx(_("failed to allocate output column")); |
09f9a393 | 366 | goto done; |
8fb6a2de LP |
367 | } |
368 | } | |
369 | ||
09f9a393 KZ |
370 | /* fill-in table with data |
371 | * -- one line for each supported flag (option) */ | |
372 | flags = wd->ident.options; | |
8fb6a2de | 373 | |
09f9a393 KZ |
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) | |
fe7af530 | 378 | add_flag_line(table, wd, &wdflags[i]); |
8fb6a2de | 379 | |
09f9a393 | 380 | flags &= ~wdflags[i].flag; |
8fb6a2de LP |
381 | } |
382 | ||
09f9a393 | 383 | if (flags) |
e4d511d4 | 384 | warnx(_("%s: unknown flags 0x%x\n"), wd->devpath, flags); |
8fb6a2de | 385 | |
fe7af530 | 386 | scols_print_table(table); |
09f9a393 KZ |
387 | rc = 0; |
388 | done: | |
fe7af530 | 389 | scols_unref_table(table); |
09f9a393 KZ |
390 | return rc; |
391 | } | |
152b47d2 | 392 | |
fb8b62df HH |
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 | */ | |
7d64a2b5 | 399 | static int set_watchdog(struct wd_control *ctl, struct wd_device *wd) |
fb8b62df HH |
400 | { |
401 | int fd; | |
402 | sigset_t sigs, oldsigs; | |
403 | int rc = 0; | |
404 | ||
7d64a2b5 | 405 | assert(wd); |
e4d511d4 | 406 | assert(wd->devpath); |
7d64a2b5 | 407 | assert(ctl); |
fb8b62df | 408 | |
fe7f7399 | 409 | if (!ctl->set_timeout && !ctl->set_pretimeout) |
c0762da1 KZ |
410 | goto sysfs_only; |
411 | ||
fb8b62df HH |
412 | sigemptyset(&oldsigs); |
413 | sigfillset(&sigs); | |
414 | sigprocmask(SIG_BLOCK, &sigs, &oldsigs); | |
415 | ||
e4d511d4 | 416 | fd = open(wd->devpath, O_WRONLY|O_CLOEXEC); |
fb8b62df HH |
417 | |
418 | if (fd < 0) { | |
419 | if (errno == EBUSY) | |
420 | warnx(_("%s: watchdog already in use, terminating."), | |
e4d511d4 KZ |
421 | wd->devpath); |
422 | warn(_("cannot open %s"), wd->devpath); | |
fb8b62df HH |
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) { | |
e4d511d4 | 434 | warn(_("%s: failed to disarm watchdog"), wd->devpath); |
fb8b62df HH |
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 | ||
7d64a2b5 KZ |
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); | |
fb8b62df HH |
449 | } |
450 | ||
7d64a2b5 KZ |
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); | |
152b47d2 KZ |
459 | } |
460 | ||
4aaabaa9 | 461 | if (close(fd)) |
3757e57f | 462 | warn(_("write failed")); |
fb8b62df | 463 | |
7d64a2b5 | 464 | sigprocmask(SIG_SETMASK, &oldsigs, NULL); |
c0762da1 KZ |
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 | ||
fb8b62df HH |
479 | return rc; |
480 | } | |
8fb6a2de | 481 | |
09f9a393 KZ |
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 | */ | |
b3dd29d1 | 488 | static int read_watchdog_from_device(struct wd_device *wd) |
09f9a393 KZ |
489 | { |
490 | int fd; | |
491 | sigset_t sigs, oldsigs; | |
492 | ||
e4d511d4 | 493 | assert(wd->devpath); |
09f9a393 KZ |
494 | |
495 | sigemptyset(&oldsigs); | |
496 | sigfillset(&sigs); | |
497 | sigprocmask(SIG_BLOCK, &sigs, &oldsigs); | |
8fb6a2de | 498 | |
e4d511d4 | 499 | fd = open(wd->devpath, O_WRONLY|O_CLOEXEC); |
8fb6a2de | 500 | |
b3dd29d1 KZ |
501 | if (fd < 0) |
502 | return -errno; | |
8fb6a2de | 503 | |
09f9a393 | 504 | if (ioctl(fd, WDIOC_GETSUPPORT, &wd->ident) < 0) |
e4d511d4 | 505 | warn(_("%s: failed to get information about watchdog"), wd->devpath); |
09f9a393 KZ |
506 | else { |
507 | ioctl(fd, WDIOC_GETSTATUS, &wd->status); | |
508 | ioctl(fd, WDIOC_GETBOOTSTATUS, &wd->bstatus); | |
509 | ||
b1b0259f HG |
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 | ||
09f9a393 KZ |
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 | } | |
8fb6a2de LP |
524 | |
525 | for (;;) { | |
526 | /* We just opened this to query the state, not to arm | |
527 | * it hence use the magic close character */ | |
8fb6a2de LP |
528 | static const char v = 'V'; |
529 | ||
530 | if (write(fd, &v, 1) >= 0) | |
531 | break; | |
8fb6a2de | 532 | if (errno != EINTR) { |
e4d511d4 | 533 | warn(_("%s: failed to disarm watchdog"), wd->devpath); |
8fb6a2de LP |
534 | break; |
535 | } | |
8fb6a2de LP |
536 | /* Let's try hard, since if we don't get this right |
537 | * the machine might end up rebooting. */ | |
538 | } | |
539 | ||
4aaabaa9 | 540 | if (close(fd)) |
3757e57f | 541 | warn(_("write failed")); |
09f9a393 KZ |
542 | sigprocmask(SIG_SETMASK, &oldsigs, NULL); |
543 | ||
544 | return 0; | |
545 | } | |
546 | ||
45303291 KZ |
547 | |
548 | /* Returns: <0 error, 0 success, 1 unssuported */ | |
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 | ||
6de3def8 TW |
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; | |
b3dd29d1 KZ |
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 | ||
b3dd29d1 | 576 | return 0; |
b3dd29d1 KZ |
577 | } |
578 | ||
86ffb5a7 KZ |
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 | 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 | ||
6de3def8 TW |
609 | static bool should_read_from_device(struct wd_device *wd) |
610 | { | |
611 | if (!wd->has_nowayout) | |
612 | return false; | |
613 | ||
614 | if (wd->nowayout) | |
615 | return false; | |
616 | ||
617 | return !wd->has_identity || | |
618 | !wd->has_fw_version || | |
619 | !wd->has_options || | |
620 | !wd->has_status || | |
621 | !wd->has_bootstatus || | |
622 | !wd->has_timeout || | |
623 | !wd->has_timeleft; | |
624 | // pretimeout attribute may be hidden in sysfs | |
625 | } | |
626 | ||
b3dd29d1 KZ |
627 | static int read_watchdog(struct wd_device *wd) |
628 | { | |
6de3def8 TW |
629 | int rc; |
630 | ||
631 | rc = read_watchdog_from_sysfs(wd); | |
b3dd29d1 | 632 | |
6de3def8 TW |
633 | if (rc && should_read_from_device(wd)) |
634 | rc = read_watchdog_from_device(wd); | |
b3dd29d1 KZ |
635 | |
636 | if (rc) { | |
637 | warn(_("cannot read information about %s"), wd->devpath); | |
638 | return -1; | |
639 | } | |
640 | ||
86ffb5a7 | 641 | read_governors(wd); |
b3dd29d1 KZ |
642 | return 0; |
643 | } | |
644 | ||
5d628f37 KZ |
645 | static void show_timeouts(struct wd_device *wd) |
646 | { | |
647 | if (wd->has_timeout) | |
648 | printf(P_("%-14s %2i second\n", "%-14s %2i seconds\n", wd->timeout), | |
649 | _("Timeout:"), wd->timeout); | |
5d628f37 KZ |
650 | if (wd->has_timeleft) |
651 | printf(P_("%-14s %2i second\n", "%-14s %2i seconds\n", wd->timeleft), | |
652 | _("Timeleft:"), wd->timeleft); | |
86ffb5a7 KZ |
653 | if (wd->has_pretimeout) |
654 | printf(P_("%-14s %2i second\n", "%-14s %2i seconds\n", wd->pretimeout), | |
655 | _("Pre-timeout:"), wd->pretimeout); | |
656 | } | |
657 | ||
658 | static void show_governors(struct wd_device *wd) | |
659 | { | |
660 | if (wd->governor) | |
661 | printf(_("%-14s %s\n"), _("Pre-timeout governor:"), wd->governor); | |
662 | if (wd->available_governors) { | |
663 | char *tmp = strv_join(wd->available_governors, " "); | |
664 | ||
665 | if (tmp) | |
666 | printf(_("%-14s %s\n"), | |
667 | _("Available pre-timeout governors:"), tmp); | |
668 | free(tmp); | |
669 | } | |
5d628f37 KZ |
670 | } |
671 | ||
a599d137 | 672 | static void print_oneline(struct wd_control *ctl, struct wd_device *wd, uint32_t wanted) |
9561d1af | 673 | { |
e4d511d4 | 674 | printf("%s:", wd->devpath); |
9561d1af | 675 | |
a599d137 | 676 | if (!ctl->hide_ident) { |
9561d1af KZ |
677 | printf(" VERSION=\"%x\"", wd->ident.firmware_version); |
678 | ||
679 | printf(" IDENTITY="); | |
fe7af530 | 680 | fputs_quoted((char *) wd->ident.identity, stdout); |
9561d1af | 681 | } |
a599d137 | 682 | if (!ctl->hide_timeouts) { |
9561d1af KZ |
683 | if (wd->has_timeout) |
684 | printf(" TIMEOUT=\"%i\"", wd->timeout); | |
685 | if (wd->has_pretimeout) | |
686 | printf(" PRETIMEOUT=\"%i\"", wd->pretimeout); | |
687 | if (wd->has_timeleft) | |
688 | printf(" TIMELEFT=\"%i\"", wd->timeleft); | |
689 | } | |
690 | ||
a599d137 | 691 | if (!ctl->hide_flags) { |
9561d1af KZ |
692 | size_t i; |
693 | uint32_t flags = wd->ident.options; | |
694 | ||
695 | for (i = 0; i < ARRAY_SIZE(wdflags); i++) { | |
696 | const struct wdflag *fl; | |
697 | ||
698 | if ((wanted && !(wanted & wdflags[i].flag)) || | |
699 | !(flags & wdflags[i].flag)) | |
700 | continue; | |
701 | ||
702 | fl= &wdflags[i]; | |
703 | ||
704 | printf(" %s=\"%s\"", fl->name, | |
705 | wd->status & fl->flag ? "1" : "0"); | |
706 | printf(" %s_BOOT=\"%s\"", fl->name, | |
707 | wd->bstatus & fl->flag ? "1" : "0"); | |
708 | ||
709 | } | |
710 | } | |
711 | ||
712 | fputc('\n', stdout); | |
713 | } | |
714 | ||
5d628f37 | 715 | static void print_device(struct wd_control *ctl, struct wd_device *wd, uint32_t wanted) |
09f9a393 | 716 | { |
5d628f37 KZ |
717 | /* NAME=value one line output */ |
718 | if (ctl->show_oneline) { | |
719 | print_oneline(ctl, wd, wanted); | |
720 | return; | |
721 | } | |
722 | ||
723 | /* pretty output */ | |
724 | if (!ctl->hide_ident) { | |
725 | printf("%-15s%s\n", _("Device:"), wd->devpath); | |
726 | printf("%-15s%s [%s %x]\n", | |
727 | _("Identity:"), | |
728 | wd->ident.identity, | |
729 | _("version"), | |
730 | wd->ident.firmware_version); | |
731 | } | |
732 | if (!ctl->hide_timeouts) | |
733 | show_timeouts(wd); | |
734 | ||
86ffb5a7 KZ |
735 | show_governors(wd); |
736 | ||
5d628f37 KZ |
737 | if (!ctl->hide_flags) |
738 | show_flags(ctl, wd, wanted); | |
09f9a393 | 739 | } |
8fb6a2de | 740 | |
09f9a393 KZ |
741 | int main(int argc, char *argv[]) |
742 | { | |
e4d511d4 | 743 | struct wd_device wd; |
a599d137 | 744 | struct wd_control ctl = { .hide_headings = 0 }; |
fe7af530 | 745 | int c, res = EXIT_SUCCESS, count = 0; |
f36f3251 | 746 | unsigned long wanted = 0; |
8c8df421 | 747 | const char *dflt_device = NULL; |
09f9a393 KZ |
748 | |
749 | static const struct option long_opts[] = { | |
09f9a393 | 750 | { "flags", required_argument, NULL, 'f' }, |
96747e75 | 751 | { "flags-only", no_argument, NULL, 'x' }, |
09f9a393 KZ |
752 | { "help", no_argument, NULL, 'h' }, |
753 | { "noflags", no_argument, NULL, 'F' }, | |
754 | { "noheadings", no_argument, NULL, 'n' }, | |
755 | { "noident", no_argument, NULL, 'I' }, | |
756 | { "notimeouts", no_argument, NULL, 'T' }, | |
fb8b62df | 757 | { "settimeout", required_argument, NULL, 's' }, |
152b47d2 | 758 | { "setpretimeout", required_argument, NULL, 'p' }, |
c0762da1 | 759 | { "setpregovernor", required_argument, NULL, 'g' }, |
09f9a393 | 760 | { "output", required_argument, NULL, 'o' }, |
9561d1af | 761 | { "oneline", no_argument, NULL, 'O' }, |
09f9a393 KZ |
762 | { "raw", no_argument, NULL, 'r' }, |
763 | { "version", no_argument, NULL, 'V' }, | |
764 | { NULL, 0, NULL, 0 } | |
765 | }; | |
766 | ||
a7349ee3 | 767 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ |
1c608be1 KZ |
768 | { 'F','f' }, /* noflags,flags*/ |
769 | { 0 } | |
770 | }; | |
771 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
772 | ||
09f9a393 KZ |
773 | setlocale(LC_ALL, ""); |
774 | bindtextdomain(PACKAGE, LOCALEDIR); | |
775 | textdomain(PACKAGE); | |
2c308875 | 776 | close_stdout_atexit(); |
09f9a393 KZ |
777 | |
778 | while ((c = getopt_long(argc, argv, | |
c0762da1 | 779 | "d:f:g:hFnITp:o:s:OrVx", long_opts, NULL)) != -1) { |
1c608be1 KZ |
780 | |
781 | err_exclusive_options(c, long_opts, excl, excl_st); | |
782 | ||
09f9a393 | 783 | switch(c) { |
09f9a393 KZ |
784 | case 'o': |
785 | ncolumns = string_to_idarray(optarg, | |
786 | columns, ARRAY_SIZE(columns), | |
787 | column2id); | |
788 | if (ncolumns < 0) | |
789 | return EXIT_FAILURE; | |
790 | break; | |
fb8b62df | 791 | case 's': |
7d64a2b5 KZ |
792 | ctl.timeout = strtos32_or_err(optarg, _("invalid timeout argument")); |
793 | ctl.set_timeout = 1; | |
152b47d2 KZ |
794 | break; |
795 | case 'p': | |
7d64a2b5 KZ |
796 | ctl.pretimeout = strtos32_or_err(optarg, _("invalid pretimeout argument")); |
797 | ctl.set_pretimeout = 1; | |
fb8b62df | 798 | break; |
09f9a393 | 799 | case 'f': |
f36f3251 | 800 | if (string_to_bitmask(optarg, &wanted, name2bit) != 0) |
09f9a393 KZ |
801 | return EXIT_FAILURE; |
802 | break; | |
09f9a393 | 803 | case 'F': |
a599d137 | 804 | ctl.hide_flags = 1; |
09f9a393 | 805 | break; |
c0762da1 KZ |
806 | case 'g': |
807 | ctl.governor = optarg; | |
808 | break; | |
09f9a393 | 809 | case 'I': |
a599d137 | 810 | ctl.hide_ident = 1; |
09f9a393 KZ |
811 | break; |
812 | case 'T': | |
a599d137 | 813 | ctl.hide_timeouts = 1; |
09f9a393 KZ |
814 | break; |
815 | case 'n': | |
a599d137 | 816 | ctl.hide_headings = 1; |
09f9a393 KZ |
817 | break; |
818 | case 'r': | |
a599d137 | 819 | ctl.show_raw = 1; |
09f9a393 | 820 | break; |
9561d1af | 821 | case 'O': |
a599d137 | 822 | ctl.show_oneline = 1; |
09f9a393 | 823 | break; |
96747e75 | 824 | case 'x': |
a599d137 KZ |
825 | ctl.hide_ident = 1; |
826 | ctl.hide_timeouts = 1; | |
96747e75 | 827 | break; |
2c308875 KZ |
828 | |
829 | case 'h': | |
830 | usage(); | |
831 | case 'V': | |
832 | print_version(EXIT_SUCCESS); | |
09f9a393 | 833 | default: |
677ec86c | 834 | errtryhelp(EXIT_FAILURE); |
09f9a393 KZ |
835 | } |
836 | } | |
837 | ||
09f9a393 KZ |
838 | if (!ncolumns) { |
839 | /* default columns */ | |
840 | columns[ncolumns++] = COL_FLAG; | |
841 | columns[ncolumns++] = COL_DESC; | |
842 | columns[ncolumns++] = COL_STATUS; | |
843 | columns[ncolumns++] = COL_BSTATUS; | |
844 | } | |
845 | ||
8c8df421 KZ |
846 | /* Device no specified, use default. */ |
847 | if (optind == argc) { | |
848 | dflt_device = get_default_device(); | |
849 | if (!dflt_device) | |
850 | err(EXIT_FAILURE, _("No default device is available.")); | |
851 | } | |
852 | ||
f56338b4 KZ |
853 | do { |
854 | int rc; | |
09f9a393 | 855 | |
f56338b4 | 856 | memset(&wd, 0, sizeof(wd)); |
8c8df421 | 857 | wd.devpath = dflt_device ? dflt_device : argv[optind++]; |
f56338b4 KZ |
858 | |
859 | if (count) | |
860 | fputc('\n', stdout); | |
861 | count++; | |
862 | ||
7d64a2b5 KZ |
863 | if (want_set(&ctl)) { |
864 | rc = set_watchdog(&ctl, &wd); | |
fb8b62df HH |
865 | if (rc) { |
866 | res = EXIT_FAILURE; | |
867 | } | |
868 | } | |
869 | ||
f56338b4 KZ |
870 | rc = read_watchdog(&wd); |
871 | if (rc) { | |
872 | res = EXIT_FAILURE; | |
873 | continue; | |
874 | } | |
875 | ||
5d628f37 | 876 | print_device(&ctl, &wd, wanted); |
7d64a2b5 | 877 | ul_unref_path(wd.sysfs); |
f56338b4 KZ |
878 | } while (optind < argc); |
879 | ||
880 | return res; | |
8fb6a2de | 881 | } |