]>
Commit | Line | Data |
---|---|---|
5ef05369 KZ |
1 | /* |
2 | * dmesg.c -- Print out the contents of the kernel ring buffer | |
7eda085c | 3 | * |
5ef05369 KZ |
4 | * Copyright (C) 1993 Theodore Ts'o <tytso@athena.mit.edu> |
5 | * Copyright (C) 2011 Karel Zak <kzak@redhat.com> | |
6 | * | |
7 | * This program comes with ABSOLUTELY NO WARRANTY. | |
6dbe3af9 | 8 | */ |
6dbe3af9 KZ |
9 | #include <linux/unistd.h> |
10 | #include <stdio.h> | |
11 | #include <getopt.h> | |
fd6b7a7f | 12 | #include <stdlib.h> |
15673c15 | 13 | #include <sys/klog.h> |
f06ec64f | 14 | #include <sys/syslog.h> |
bd304d92 | 15 | #include <sys/time.h> |
42fac79a | 16 | #include <sys/sysinfo.h> |
15103c4b | 17 | #include <ctype.h> |
bd304d92 | 18 | #include <time.h> |
5423ccb1 | 19 | |
15103c4b | 20 | #include "c.h" |
5423ccb1 | 21 | #include "nls.h" |
15673c15 | 22 | #include "strutils.h" |
15103c4b | 23 | #include "xalloc.h" |
b8300c0a KZ |
24 | #include "widechar.h" |
25 | #include "writeall.h" | |
636a6207 | 26 | #include "bitops.h" |
6dbe3af9 | 27 | |
59a14899 KZ |
28 | /* Close the log. Currently a NOP. */ |
29 | #define SYSLOG_ACTION_CLOSE 0 | |
30 | /* Open the log. Currently a NOP. */ | |
31 | #define SYSLOG_ACTION_OPEN 1 | |
32 | /* Read from the log. */ | |
33 | #define SYSLOG_ACTION_READ 2 | |
34 | /* Read all messages remaining in the ring buffer. (allowed for non-root) */ | |
35 | #define SYSLOG_ACTION_READ_ALL 3 | |
36 | /* Read and clear all messages remaining in the ring buffer */ | |
37 | #define SYSLOG_ACTION_READ_CLEAR 4 | |
38 | /* Clear ring buffer. */ | |
39 | #define SYSLOG_ACTION_CLEAR 5 | |
40 | /* Disable printk's to console */ | |
41 | #define SYSLOG_ACTION_CONSOLE_OFF 6 | |
42 | /* Enable printk's to console */ | |
43 | #define SYSLOG_ACTION_CONSOLE_ON 7 | |
44 | /* Set level of messages printed to console */ | |
45 | #define SYSLOG_ACTION_CONSOLE_LEVEL 8 | |
46 | /* Return number of unread characters in the log buffer */ | |
47 | #define SYSLOG_ACTION_SIZE_UNREAD 9 | |
48 | /* Return size of the log buffer */ | |
49 | #define SYSLOG_ACTION_SIZE_BUFFER 10 | |
50 | ||
f06ec64f | 51 | /* |
5ef05369 | 52 | * Priority and facility names |
f06ec64f KZ |
53 | */ |
54 | struct dmesg_name { | |
55 | const char *name; | |
56 | const char *help; | |
57 | }; | |
58 | ||
5ef05369 KZ |
59 | /* |
60 | * Priority names -- based on sys/syslog.h | |
61 | */ | |
f06ec64f KZ |
62 | static const struct dmesg_name level_names[] = |
63 | { | |
64 | [LOG_EMERG] = { "emerg", N_("system is unusable") }, | |
65 | [LOG_ALERT] = { "alert", N_("action must be taken immediately") }, | |
66 | [LOG_CRIT] = { "crit", N_("critical conditions") }, | |
67 | [LOG_ERR] = { "err", N_("error conditions") }, | |
68 | [LOG_WARNING] = { "warn", N_("warning conditions") }, | |
69 | [LOG_NOTICE] = { "notice",N_("normal but significant condition") }, | |
70 | [LOG_INFO] = { "info", N_("informational") }, | |
71 | [LOG_DEBUG] = { "debug", N_("debug-level messages") } | |
72 | }; | |
73 | ||
85f3cc55 KZ |
74 | /* |
75 | * sys/syslog.h uses (f << 3) for all facility codes. | |
76 | * We want to use the codes as array idexes, so shift back... | |
77 | * | |
78 | * Note that libc LOG_FAC() macro returns the base codes, not the | |
79 | * shifted code :-) | |
80 | */ | |
81 | #define FAC_BASE(f) ((f) >> 3) | |
82 | ||
83 | static const struct dmesg_name facility_names[] = | |
84 | { | |
85 | [FAC_BASE(LOG_KERN)] = { "kern", N_("kernel messages") }, | |
86 | [FAC_BASE(LOG_USER)] = { "user", N_("random user-level messages") }, | |
87 | [FAC_BASE(LOG_MAIL)] = { "mail", N_("mail system") }, | |
88 | [FAC_BASE(LOG_DAEMON)] = { "daemon", N_("system daemons") }, | |
89 | [FAC_BASE(LOG_AUTH)] = { "auth", N_("security/authorization messages") }, | |
90 | [FAC_BASE(LOG_SYSLOG)] = { "syslog", N_("messages generated internally by syslogd") }, | |
91 | [FAC_BASE(LOG_LPR)] = { "lpr", N_("line printer subsystem") }, | |
92 | [FAC_BASE(LOG_NEWS)] = { "news", N_("network news subsystem") }, | |
93 | [FAC_BASE(LOG_UUCP)] = { "uucp", N_("UUCP subsystem") }, | |
94 | [FAC_BASE(LOG_CRON)] = { "cron", N_("clock daemon") }, | |
95 | [FAC_BASE(LOG_AUTHPRIV)] = { "authpriv", N_("security/authorization messages (private)") }, | |
96 | [FAC_BASE(LOG_FTP)] = { "ftp", N_("ftp daemon") }, | |
97 | }; | |
98 | ||
aca1633a KZ |
99 | struct dmesg_control { |
100 | /* bit arrays -- see include/bitops.h */ | |
101 | char levels[ARRAY_SIZE(level_names) / NBBY + 1]; | |
102 | char facilities[ARRAY_SIZE(facility_names) / NBBY + 1]; | |
103 | ||
bd304d92 | 104 | struct timeval lasttime; /* last printed timestamp */ |
42fac79a | 105 | time_t boot_time; /* system boot time */ |
bd304d92 | 106 | |
aca1633a KZ |
107 | int raw:1; /* raw mode */ |
108 | int fltr_lev:1; /* filter out by levels[] */ | |
109 | int fltr_fac:1; /* filter out by facilities[] */ | |
110 | int decode:1; /* use "facility: level: " prefix */ | |
111 | int notime:1; /* don't print timestamp */ | |
bd304d92 | 112 | int delta:1; /* show time deltas */ |
42fac79a | 113 | int ctime:1; /* show human readable time */ |
aca1633a | 114 | }; |
f4fa5b44 | 115 | |
a7ee94f2 KZ |
116 | struct dmesg_record { |
117 | const char *mesg; | |
118 | size_t mesg_size; | |
119 | ||
120 | int level; | |
121 | int facility; | |
bd304d92 | 122 | struct timeval tv; |
a7ee94f2 KZ |
123 | |
124 | const char *next; /* buffer with next unparsed record */ | |
125 | size_t next_size; /* size of the next buffer */ | |
126 | }; | |
127 | ||
4a3b7949 | 128 | static void __attribute__((__noreturn__)) usage(FILE *out) |
15103c4b | 129 | { |
f06ec64f KZ |
130 | int i; |
131 | ||
4a3b7949 KZ |
132 | fprintf(out, _( |
133 | "\nUsage:\n" | |
134 | " %s [options]\n"), program_invocation_short_name); | |
135 | ||
136 | fprintf(out, _( | |
137 | "\nOptions:\n" | |
04199860 | 138 | " -C, --clear clear the kernel ring buffer\n" |
4a3b7949 | 139 | " -c, --read-clear read and clear all messages\n" |
bd304d92 KZ |
140 | " -D, --console-off disable printing messages to console\n" |
141 | " -d, --show-delta show time delta between printed messages\n" | |
142 | " -E, --console-on enable printing messages to console\n" | |
0e24df3b | 143 | " -f, --facility=LIST restrict output to defined facilities\n" |
2170174e | 144 | " -h, --help display this help and exit\n" |
25000180 | 145 | " -k, --kernel display kernel messages\n" |
636a6207 | 146 | " -l, --level=LIST restrict output to defined levels\n" |
2170174e | 147 | " -n, --console-level=LEVEL set level of messages printed to console\n" |
4a3b7949 KZ |
148 | " -r, --raw print the raw message buffer\n" |
149 | " -s, --buffer-size=SIZE buffer size to query the kernel ring buffer\n" | |
42fac79a KZ |
150 | " -T, --ctime show human readable timestamp (could be \n" |
151 | " inaccurate if you have used SUSPEND/RESUME)\n" | |
d74b8dfc | 152 | " -t, --notime don't print messages timestamp\n" |
25000180 | 153 | " -u, --userspace display userspace messages\n" |
872a1575 KZ |
154 | " -V, --version output version information and exit\n" |
155 | " -x, --decode decode facility and level to readable string\n\n")); | |
4a3b7949 | 156 | |
85f3cc55 | 157 | fprintf(out, _("Supported log facilities:\n")); |
f06ec64f | 158 | for (i = 0; i < ARRAY_SIZE(level_names); i++) { |
85f3cc55 KZ |
159 | fprintf(stderr, " %7s - %s\n", |
160 | facility_names[i].name, | |
161 | _(facility_names[i].help)); | |
162 | } | |
163 | ||
164 | fprintf(out, _("\nSupported log levels (priorities):\n")); | |
165 | for (i = 0; i < ARRAY_SIZE(level_names); i++) { | |
166 | fprintf(stderr, " %7s - %s\n", | |
f06ec64f KZ |
167 | level_names[i].name, |
168 | _(level_names[i].help)); | |
169 | } | |
170 | ||
171 | fputc('\n', out); | |
172 | ||
4a3b7949 | 173 | exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); |
6dbe3af9 KZ |
174 | } |
175 | ||
5ef05369 KZ |
176 | /* |
177 | * LEVEL ::= <number> | <name> | |
178 | * <number> ::= number in range <0..N>, where N < ARRAY_SIZE(level_names) | |
179 | * <name> ::= case-insensitive text | |
180 | */ | |
5c8f6bc6 | 181 | static int parse_level(const char *str, size_t len) |
f06ec64f KZ |
182 | { |
183 | int i; | |
184 | ||
5c8f6bc6 | 185 | if (!str) |
f06ec64f | 186 | return -1; |
5c8f6bc6 KZ |
187 | if (!len) |
188 | len = strlen(str); | |
189 | errno = 0; | |
f06ec64f | 190 | |
5c8f6bc6 KZ |
191 | if (isdigit(*str)) { |
192 | char *end = NULL; | |
193 | ||
194 | i = strtol(str, &end, 10); | |
195 | if (!errno && end && end > str && end - str == len && | |
196 | i >= 0 && i < ARRAY_SIZE(level_names)) | |
f06ec64f | 197 | return i; |
5c8f6bc6 KZ |
198 | } else { |
199 | for (i = 0; i < ARRAY_SIZE(level_names); i++) { | |
200 | const char *n = level_names[i].name; | |
201 | ||
202 | if (strncasecmp(str, n, len) == 0 && *(n + len) == '\0') | |
203 | return i; | |
204 | } | |
205 | } | |
206 | ||
207 | if (errno) | |
208 | err(EXIT_FAILURE, _("failed to parse level '%s'"), str); | |
209 | ||
210 | errx(EXIT_FAILURE, _("unknown level '%s'"), str); | |
f06ec64f KZ |
211 | return -1; |
212 | } | |
213 | ||
5ef05369 KZ |
214 | /* |
215 | * FACILITY ::= <number> | <name> | |
216 | * <number> ::= number in range <0..N>, where N < ARRAY_SIZE(facility_names) | |
217 | * <name> ::= case-insensitive text | |
218 | */ | |
0e24df3b KZ |
219 | static int parse_facility(const char *str, size_t len) |
220 | { | |
221 | int i; | |
222 | ||
223 | if (!str) | |
224 | return -1; | |
225 | if (!len) | |
226 | len = strlen(str); | |
227 | errno = 0; | |
228 | ||
229 | if (isdigit(*str)) { | |
230 | char *end = NULL; | |
231 | ||
232 | i = strtol(str, &end, 10); | |
233 | if (!errno && end && end > str && end - str == len && | |
234 | i >= 0 && i < ARRAY_SIZE(facility_names)) | |
235 | return i; | |
236 | } else { | |
237 | for (i = 0; i < ARRAY_SIZE(facility_names); i++) { | |
238 | const char *n = facility_names[i].name; | |
239 | ||
240 | if (strncasecmp(str, n, len) == 0 && *(n + len) == '\0') | |
241 | return i; | |
242 | } | |
243 | } | |
244 | ||
245 | if (errno) | |
246 | err(EXIT_FAILURE, _("failed to parse facility '%s'"), str); | |
247 | ||
248 | errx(EXIT_FAILURE, _("unknown facility '%s'"), str); | |
249 | return -1; | |
250 | } | |
251 | ||
5ef05369 KZ |
252 | /* |
253 | * Parses numerical prefix used for all messages in kernel ring buffer. | |
254 | * | |
255 | * Priorities/facilities are encoded into a single 32-bit quantity, where the | |
256 | * bottom 3 bits are the priority (0-7) and the top 28 bits are the facility | |
257 | * (0-big number). | |
258 | * | |
259 | * Note that the number has to end with '>' char. | |
260 | */ | |
636a6207 KZ |
261 | static const char *parse_faclev(const char *str, int *fac, int *lev) |
262 | { | |
263 | long num; | |
264 | char *end = NULL; | |
265 | ||
266 | if (!str) | |
267 | return str; | |
268 | ||
269 | errno = 0; | |
270 | num = strtol(str, &end, 10); | |
271 | ||
272 | if (!errno && end && end > str) { | |
273 | *fac = LOG_FAC(num); | |
274 | *lev = LOG_PRI(num); | |
85f3cc55 KZ |
275 | |
276 | if (*lev > ARRAY_SIZE(level_names)) | |
277 | *lev = -1; | |
278 | if (*fac > ARRAY_SIZE(facility_names)) | |
279 | *fac = -1; | |
636a6207 KZ |
280 | return end + 1; /* skip '<' */ |
281 | } | |
282 | ||
283 | return str; | |
284 | } | |
285 | ||
bd304d92 KZ |
286 | static const char *parse_timestamp(const char *str0, struct timeval *tv) |
287 | { | |
288 | const char *str = str0; | |
289 | char *end = NULL; | |
290 | ||
291 | if (!str0) | |
292 | return str0; | |
293 | ||
294 | errno = 0; | |
295 | tv->tv_sec = strtol(str, &end, 10); | |
296 | ||
297 | if (!errno && end && *end == '.' && *(end + 1)) { | |
298 | str = end + 1; | |
299 | end = NULL; | |
300 | tv->tv_usec = strtol(str, &end, 10); | |
301 | } | |
302 | if (errno || !end || end == str || *end != ']') | |
303 | return str0; | |
304 | ||
305 | return end + 1; /* skip ']' */ | |
306 | } | |
307 | ||
308 | ||
309 | static double time_diff(struct timeval *a, struct timeval *b) | |
310 | { | |
311 | return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / 1E6; | |
312 | } | |
313 | ||
5ef05369 KZ |
314 | /* |
315 | * LIST ::= <item> [, <item>] | |
316 | * | |
317 | * The <item> is translated to 'id' by name2id() function and the 'id' is used | |
318 | * as a possition in the 'ary' bit array. It means that the 'id' has to be in | |
319 | * range <0..N> where N < sizeof(ary) * NBBY. | |
320 | */ | |
636a6207 KZ |
321 | static int list_to_bitarray(const char *list, |
322 | int (*name2id)(const char *name, size_t namesz), | |
323 | char *ary) | |
324 | { | |
325 | const char *begin = NULL, *p; | |
326 | ||
327 | for (p = list; p && *p; p++) { | |
328 | const char *end = NULL; | |
329 | int id; | |
330 | ||
331 | if (!begin) | |
332 | begin = p; /* begin of the level name */ | |
333 | if (*p == ',') | |
334 | end = p; /* terminate the name */ | |
335 | if (*(p + 1) == '\0') | |
336 | end = p + 1; /* end of string */ | |
337 | if (!begin || !end) | |
338 | continue; | |
339 | if (end <= begin) | |
340 | return -1; | |
341 | ||
342 | id = name2id(begin, end - begin); | |
343 | if (id < 0) | |
344 | return id; | |
345 | setbit(ary, id); | |
346 | begin = NULL; | |
347 | if (end && !*end) | |
348 | break; | |
349 | } | |
350 | return 0; | |
351 | } | |
352 | ||
eed99b2a KZ |
353 | static int get_buffer_size() |
354 | { | |
355 | int n = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0); | |
356 | ||
357 | return n > 0 ? n : 0; | |
358 | } | |
359 | ||
42fac79a KZ |
360 | static time_t get_boot_time(void) |
361 | { | |
362 | struct sysinfo info; | |
363 | struct timeval tv; | |
364 | ||
365 | if (sysinfo(&info) != 0) | |
366 | warn(_("sysinfo failed")); | |
367 | else if (gettimeofday(&tv, NULL) != 0) | |
368 | warn(_("gettimeofday failed")); | |
369 | else | |
370 | return tv.tv_sec -= info.uptime; | |
371 | return 0; | |
372 | } | |
373 | ||
5ef05369 KZ |
374 | /* |
375 | * Reads messages from kernel ring buffer | |
376 | */ | |
65e3eed9 KZ |
377 | static int read_buffer(char **buf, size_t bufsize, int clear) |
378 | { | |
379 | size_t sz; | |
380 | int rc = -1; | |
381 | int cmd = clear ? SYSLOG_ACTION_READ_CLEAR : | |
382 | SYSLOG_ACTION_READ_ALL; | |
383 | ||
384 | if (bufsize) { | |
385 | sz = bufsize + 8; | |
386 | *buf = xmalloc(sz * sizeof(char)); | |
387 | rc = klogctl(cmd, *buf, sz); | |
388 | } else { | |
389 | sz = 16392; | |
390 | while (1) { | |
391 | *buf = xmalloc(sz * sizeof(char)); | |
392 | rc = klogctl(SYSLOG_ACTION_READ_ALL, *buf, sz); | |
393 | if (rc != sz || sz > (1 << 28)) | |
394 | break; | |
395 | free(*buf); | |
396 | *buf = NULL; | |
397 | sz *= 4; | |
398 | } | |
399 | ||
400 | if (rc > 0 && clear) | |
401 | rc = klogctl(SYSLOG_ACTION_READ_CLEAR, *buf, sz); | |
402 | } | |
403 | ||
404 | return rc; | |
405 | } | |
406 | ||
b8300c0a KZ |
407 | static int fwrite_hex(const char *buf, size_t size, FILE *out) |
408 | { | |
409 | int i; | |
410 | ||
411 | for (i = 0; i < size; i++) { | |
412 | int rc = fprintf(out, "\\x%02x", buf[i]); | |
413 | if (rc < 0) | |
414 | return rc; | |
415 | } | |
416 | return 0; | |
417 | } | |
418 | ||
5ef05369 KZ |
419 | /* |
420 | * Prints to 'out' and non-printable chars are replaced with \x<hex> sequences. | |
421 | */ | |
b8300c0a KZ |
422 | static void safe_fwrite(const char *buf, size_t size, FILE *out) |
423 | { | |
424 | int i; | |
425 | #ifdef HAVE_WIDECHAR | |
426 | mbstate_t s; | |
427 | memset(&s, 0, sizeof (s)); | |
428 | #endif | |
429 | for (i = 0; i < size; i++) { | |
430 | const char *p = buf + i; | |
431 | int rc, hex = 0; | |
432 | ||
433 | #ifdef HAVE_WIDECHAR | |
434 | wchar_t wc; | |
435 | size_t len = mbrtowc(&wc, p, size - i, &s); | |
436 | ||
437 | if (len == 0) /* L'\0' */ | |
438 | return; | |
439 | ||
730d5e77 | 440 | if (len == (size_t)-1 || len == (size_t)-2) { /* invalid sequence */ |
b8300c0a KZ |
441 | memset(&s, 0, sizeof (s)); |
442 | len = hex = 1; | |
443 | ||
444 | } else if (len > 1 && !iswprint(wc)) { /* non-printable multibyte */ | |
445 | hex = 1; | |
446 | } else | |
447 | #endif | |
448 | { | |
449 | if (!isprint((unsigned int) *p) && | |
450 | !isspace((unsigned int) *p)) /* non-printable */ | |
451 | hex = 1; | |
452 | } | |
453 | if (hex) | |
454 | rc = fwrite_hex(p, len, out); | |
455 | else | |
85f3cc55 | 456 | rc = fwrite(p, 1, len, out) != len; |
b8300c0a KZ |
457 | if (rc != 0) |
458 | err(EXIT_FAILURE, _("write failed")); | |
459 | } | |
460 | } | |
461 | ||
a7ee94f2 | 462 | static int get_next_record(struct dmesg_control *ctl, struct dmesg_record *rec) |
f4fa5b44 | 463 | { |
f4fa5b44 | 464 | int i; |
b8300c0a KZ |
465 | const char *begin = NULL; |
466 | ||
a7ee94f2 KZ |
467 | if (!rec->next || !rec->next_size) |
468 | return 1; | |
f4fa5b44 | 469 | |
a7ee94f2 KZ |
470 | rec->mesg = NULL; |
471 | rec->mesg_size = 0; | |
472 | rec->facility = -1; | |
473 | rec->level = -1; | |
bd304d92 KZ |
474 | rec->tv.tv_sec = 0; |
475 | rec->tv.tv_usec = 0; | |
a7ee94f2 KZ |
476 | |
477 | for (i = 0; i < rec->next_size; i++) { | |
478 | const char *p = rec->next + i; | |
479 | const char *end = NULL; | |
b8300c0a KZ |
480 | |
481 | if (!begin) | |
482 | begin = p; | |
483 | if (*p == '\n') | |
484 | end = p; | |
a7ee94f2 | 485 | if (i + 1 == rec->next_size) { |
b8300c0a | 486 | end = p + 1; |
f4fa5b44 | 487 | i++; |
f4fa5b44 | 488 | } |
a7ee94f2 KZ |
489 | if (begin && !*begin) |
490 | begin = NULL; /* zero(s) at the end of the buffer? */ | |
b8300c0a KZ |
491 | if (!begin || !end) |
492 | continue; | |
493 | if (end <= begin) | |
494 | continue; /* error or empty line? */ | |
495 | ||
636a6207 | 496 | if (*begin == '<') { |
aca1633a | 497 | if (ctl->fltr_lev || ctl->fltr_fac || ctl->decode) { |
a7ee94f2 KZ |
498 | begin = parse_faclev(begin + 1, &rec->facility, |
499 | &rec->level); | |
636a6207 KZ |
500 | } else { |
501 | /* ignore facility and level */ | |
502 | while (begin < end) { | |
503 | begin++; | |
504 | if (*(begin - 1) == '>') | |
505 | break; | |
506 | } | |
b8300c0a KZ |
507 | } |
508 | } | |
509 | ||
a7ee94f2 KZ |
510 | if (end <= begin) |
511 | return -1; /* error */ | |
d74b8dfc | 512 | |
bd304d92 KZ |
513 | if (*begin == '[' && (*(begin + 1) == ' ' || |
514 | isdigit(*(begin + 1)))) { | |
515 | if (ctl->notime) { /* ignore timestamp */ | |
516 | while (begin < end) { | |
517 | begin++; | |
518 | if (*(begin - 1) == ']') | |
519 | break; | |
520 | } | |
42fac79a | 521 | } else if (ctl->delta || ctl->ctime) { |
bd304d92 | 522 | begin = parse_timestamp(begin + 1, &rec->tv); |
d74b8dfc | 523 | } |
a7ee94f2 | 524 | } |
d74b8dfc | 525 | |
a7ee94f2 KZ |
526 | if (begin < end && *begin == ' ') |
527 | begin++; | |
d74b8dfc | 528 | |
a7ee94f2 KZ |
529 | rec->mesg = begin; |
530 | rec->mesg_size = end - begin; | |
b8300c0a | 531 | |
a7ee94f2 KZ |
532 | rec->next_size -= end - rec->next; |
533 | rec->next = rec->next_size > 0 ? end + 1 : NULL; | |
534 | ||
535 | return 0; | |
536 | } | |
537 | ||
538 | return 1; | |
539 | } | |
540 | ||
541 | static int accept_record(struct dmesg_control *ctl, struct dmesg_record *rec) | |
542 | { | |
543 | if (ctl->fltr_lev && (rec->facility < 0 || | |
544 | !isset(ctl->levels, rec->level))) | |
545 | return 0; | |
546 | ||
547 | if (ctl->fltr_fac && (rec->facility < 0 || | |
548 | !isset(ctl->facilities, rec->facility))) | |
549 | return 0; | |
550 | ||
551 | return 1; | |
552 | } | |
553 | ||
554 | /* | |
555 | * Prints the 'buf' kernel ring buffer; the messages are filtered out according | |
556 | * to 'levels' and 'facilities' bitarrays. | |
557 | */ | |
558 | static void print_buffer(const char *buf, size_t size, | |
559 | struct dmesg_control *ctl) | |
560 | { | |
561 | struct dmesg_record rec = { .next = buf, .next_size = size }; | |
42fac79a | 562 | char tbuf[256]; |
a7ee94f2 KZ |
563 | |
564 | if (ctl->raw) { | |
565 | /* print whole buffer */ | |
566 | safe_fwrite(buf, size, stdout); | |
567 | if (buf[size - 1] != '\n') | |
568 | putchar('\n'); | |
569 | return; | |
570 | } | |
571 | ||
bd304d92 | 572 | while (get_next_record(ctl, &rec) == 0) { |
bd304d92 KZ |
573 | if (!rec.mesg_size) |
574 | continue; | |
a7ee94f2 KZ |
575 | |
576 | if (!accept_record(ctl, &rec)) | |
577 | continue; | |
578 | ||
579 | if (ctl->decode && rec.level >= 0 && rec.facility >= 0) | |
580 | printf("%-6s:%-6s: ", facility_names[rec.facility].name, | |
581 | level_names[rec.level].name); | |
582 | ||
42fac79a KZ |
583 | *tbuf = '\0'; |
584 | if (ctl->ctime) { | |
585 | time_t t = ctl->boot_time + rec.tv.tv_sec; | |
586 | if (strftime(tbuf, sizeof(tbuf), "%a %b %e %H:%M:%S %Y", | |
587 | localtime(&t)) == 0) | |
588 | *tbuf = '\0'; | |
589 | } | |
bd304d92 KZ |
590 | if (ctl->delta) { |
591 | double delta = 0; | |
592 | ||
42fac79a | 593 | if (timerisset(&ctl->lasttime)) |
bd304d92 KZ |
594 | delta = time_diff(&rec.tv, &ctl->lasttime); |
595 | ctl->lasttime = rec.tv; | |
596 | ||
42fac79a KZ |
597 | if (ctl->ctime && *tbuf) |
598 | printf("[%s ", tbuf); | |
599 | else | |
600 | printf("[%5d.%06d ", (int) rec.tv.tv_sec, | |
601 | (int) rec.tv.tv_usec); | |
602 | printf("<%12.06f>] ", delta); | |
603 | } else if (ctl->ctime && *tbuf) { | |
604 | printf("[%s] ", tbuf); | |
bd304d92 KZ |
605 | } |
606 | ||
a7ee94f2 KZ |
607 | safe_fwrite(rec.mesg, rec.mesg_size, stdout); |
608 | ||
609 | if (*(rec.mesg + rec.mesg_size - 1) != '\n') | |
610 | putchar('\n'); | |
f4fa5b44 | 611 | } |
f4fa5b44 KZ |
612 | } |
613 | ||
15103c4b MP |
614 | int main(int argc, char *argv[]) |
615 | { | |
616 | char *buf = NULL; | |
c129767e | 617 | int bufsize = 0; |
df1dddf9 KZ |
618 | int n; |
619 | int c; | |
48c5c662 | 620 | int console_level = 0; |
0506537a | 621 | int cmd = -1; |
aca1633a | 622 | static struct dmesg_control ctl; |
6dbe3af9 | 623 | |
4a3b7949 | 624 | static const struct option longopts[] = { |
4a3b7949 | 625 | { "buffer-size", required_argument, NULL, 's' }, |
5ef05369 | 626 | { "clear", no_argument, NULL, 'C' }, |
4a3b7949 | 627 | { "console-level", required_argument, NULL, 'n' }, |
bd304d92 KZ |
628 | { "console-off", no_argument, NULL, 'D' }, |
629 | { "console-on", no_argument, NULL, 'E' }, | |
5ef05369 | 630 | { "decode", no_argument, NULL, 'x' }, |
0e24df3b | 631 | { "facility", required_argument, NULL, 'f' }, |
4a3b7949 | 632 | { "help", no_argument, NULL, 'h' }, |
25000180 | 633 | { "kernel", no_argument, NULL, 'k' }, |
5ef05369 KZ |
634 | { "level", required_argument, NULL, 'l' }, |
635 | { "raw", no_argument, NULL, 'r' }, | |
636 | { "read-clear", no_argument, NULL, 'c' }, | |
bd304d92 | 637 | { "show-delta", no_argument, NULL, 'd' }, |
42fac79a | 638 | { "ctime", no_argument, NULL, 'T' }, |
d74b8dfc | 639 | { "notime", no_argument, NULL, 't' }, |
25000180 | 640 | { "userspace", no_argument, NULL, 'u' }, |
5ef05369 | 641 | { "version", no_argument, NULL, 'V' }, |
4a3b7949 KZ |
642 | { NULL, 0, NULL, 0 } |
643 | }; | |
644 | ||
df1dddf9 KZ |
645 | setlocale(LC_ALL, ""); |
646 | bindtextdomain(PACKAGE, LOCALEDIR); | |
647 | textdomain(PACKAGE); | |
7eda085c | 648 | |
42fac79a | 649 | while ((c = getopt_long(argc, argv, "CcDdEf:hkl:n:rs:TtuVx", |
aca1633a | 650 | longopts, NULL)) != -1) { |
0506537a | 651 | |
bd304d92 | 652 | if (cmd != -1 && strchr("CcnDE", c)) |
641986cf KZ |
653 | errx(EXIT_FAILURE, _("clear, read-clear, console-level, " |
654 | "console-on, and console-off options are mutually " | |
655 | "exclusive")); | |
0506537a | 656 | |
df1dddf9 | 657 | switch (c) { |
04199860 KZ |
658 | case 'C': |
659 | cmd = SYSLOG_ACTION_CLEAR; | |
660 | break; | |
df1dddf9 | 661 | case 'c': |
59a14899 | 662 | cmd = SYSLOG_ACTION_READ_CLEAR; |
df1dddf9 | 663 | break; |
bd304d92 | 664 | case 'D': |
2170174e KZ |
665 | cmd = SYSLOG_ACTION_CONSOLE_OFF; |
666 | break; | |
bd304d92 KZ |
667 | case 'd': |
668 | ctl.delta = 1; | |
669 | break; | |
670 | case 'E': | |
2170174e KZ |
671 | cmd = SYSLOG_ACTION_CONSOLE_ON; |
672 | break; | |
0e24df3b | 673 | case 'f': |
aca1633a KZ |
674 | ctl.fltr_fac = 1; |
675 | list_to_bitarray(optarg, parse_facility, ctl.facilities); | |
0e24df3b | 676 | break; |
5ef05369 KZ |
677 | case 'h': |
678 | usage(stdout); | |
df1dddf9 | 679 | break; |
25000180 | 680 | case 'k': |
aca1633a KZ |
681 | ctl.fltr_fac = 1; |
682 | setbit(ctl.facilities, FAC_BASE(LOG_KERN)); | |
25000180 | 683 | break; |
636a6207 | 684 | case 'l': |
aca1633a KZ |
685 | ctl.fltr_lev= 1; |
686 | list_to_bitarray(optarg, parse_level, ctl.levels); | |
636a6207 | 687 | break; |
5ef05369 KZ |
688 | case 'n': |
689 | cmd = SYSLOG_ACTION_CONSOLE_LEVEL; | |
690 | console_level = parse_level(optarg, 0); | |
691 | break; | |
11ea22d5 | 692 | case 'r': |
aca1633a | 693 | ctl.raw = 1; |
11ea22d5 | 694 | break; |
df1dddf9 | 695 | case 's': |
0506537a KZ |
696 | bufsize = strtol_or_err(optarg, |
697 | _("failed to parse buffer size")); | |
c129767e KZ |
698 | if (bufsize < 4096) |
699 | bufsize = 4096; | |
df1dddf9 | 700 | break; |
42fac79a KZ |
701 | case 'T': |
702 | ctl.boot_time = get_boot_time(); | |
703 | if (ctl.boot_time) | |
704 | ctl.ctime = 1; | |
705 | break; | |
d74b8dfc | 706 | case 't': |
aca1633a | 707 | ctl.notime = 1; |
d74b8dfc | 708 | break; |
25000180 | 709 | case 'u': |
aca1633a | 710 | ctl.fltr_fac = 1; |
25000180 | 711 | for (n = 1; n < ARRAY_SIZE(facility_names); n++) |
aca1633a | 712 | setbit(ctl.facilities, n); |
25000180 | 713 | break; |
4a3b7949 KZ |
714 | case 'V': |
715 | printf(_("%s from %s\n"), program_invocation_short_name, | |
716 | PACKAGE_STRING); | |
717 | return EXIT_SUCCESS; | |
5ef05369 | 718 | case 'x': |
aca1633a | 719 | ctl.decode = 1; |
4a3b7949 | 720 | break; |
df1dddf9 KZ |
721 | case '?': |
722 | default: | |
4a3b7949 | 723 | usage(stderr); |
df1dddf9 KZ |
724 | } |
725 | } | |
726 | argc -= optind; | |
727 | argv += optind; | |
25000180 | 728 | n = 0; |
e6c379eb | 729 | |
15103c4b | 730 | if (argc > 1) |
4a3b7949 | 731 | usage(stderr); |
6dbe3af9 | 732 | |
0506537a KZ |
733 | if (cmd == -1) |
734 | cmd = SYSLOG_ACTION_READ_ALL; /* default */ | |
735 | ||
641986cf KZ |
736 | if (ctl.raw && (ctl.fltr_lev || ctl.fltr_fac || ctl.delta || |
737 | ctl.notime || ctl.ctime || ctl.decode)) | |
738 | errx(EXIT_FAILURE, _("--raw can't be used together with level, " | |
739 | "facility, decode, delta, ctime or notime options")); | |
740 | ||
741 | if (ctl.notime && (ctl.ctime || ctl.delta)) | |
742 | errx(EXIT_FAILURE, _("--notime can't be used together with ctime " | |
743 | "or delta options")); | |
85f3cc55 | 744 | |
48c5c662 KZ |
745 | switch (cmd) { |
746 | case SYSLOG_ACTION_READ_ALL: | |
747 | case SYSLOG_ACTION_READ_CLEAR: | |
748 | if (!bufsize) | |
749 | bufsize = get_buffer_size(); | |
750 | n = read_buffer(&buf, bufsize, cmd == SYSLOG_ACTION_READ_CLEAR); | |
751 | if (n > 0) | |
aca1633a | 752 | print_buffer(buf, n, &ctl); |
48c5c662 KZ |
753 | free(buf); |
754 | break; | |
04199860 | 755 | case SYSLOG_ACTION_CLEAR: |
2170174e KZ |
756 | case SYSLOG_ACTION_CONSOLE_OFF: |
757 | case SYSLOG_ACTION_CONSOLE_ON: | |
04199860 KZ |
758 | n = klogctl(cmd, NULL, 0); |
759 | break; | |
48c5c662 KZ |
760 | case SYSLOG_ACTION_CONSOLE_LEVEL: |
761 | n = klogctl(cmd, NULL, console_level); | |
762 | break; | |
763 | default: | |
764 | errx(EXIT_FAILURE, _("unsupported command")); | |
765 | break; | |
df1dddf9 | 766 | } |
6dbe3af9 | 767 | |
15103c4b MP |
768 | if (n < 0) |
769 | err(EXIT_FAILURE, _("klogctl failed")); | |
6dbe3af9 | 770 | |
15103c4b | 771 | return EXIT_SUCCESS; |
6dbe3af9 | 772 | } |