]> git.proxmox.com Git - proxmox-mini-journalreader.git/blob - src/mini-journalreader.c
15865921df4db12927471b8799d44cc847a72d5c
[proxmox-mini-journalreader.git] / src / mini-journalreader.c
1 /*
2 Copyright (C) 2019 Proxmox Server Solutions GmbH
3
4 Copyright: mini-journal is under GNU GPL, the GNU General Public License.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; version 2 dated June, 1991.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 02111-1307, USA.
19
20 Author: Dominik Csapak <d.csapak@proxmox.com>
21 */
22
23 #include <errno.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <systemd/sd-journal.h>
29 #include <time.h>
30 #include <unistd.h>
31
32 #define BUFSIZE 4095
33
34 static char buf[BUFSIZE + 1];
35 static size_t offset = 0;
36
37 static uint64_t get_timestamp(sd_journal *j) {
38 uint64_t timestamp;
39 int r = sd_journal_get_realtime_usec(j, &timestamp);
40 if (r < 0) {
41 fprintf(stderr, "Failed %s\n", strerror(-r));
42 return (uint64_t)-1;
43 }
44 return timestamp;
45 }
46
47 static void print_to_buf(const char * string, size_t length) {
48 if (!length) {
49 return;
50 }
51 size_t string_offset = 0;
52 size_t remaining = length;
53 while (offset + remaining > BUFSIZE) {
54 strncpy(buf + offset, string + string_offset, BUFSIZE - offset);
55 string_offset += BUFSIZE - offset;
56 remaining = length - string_offset;
57 if (write (1, buf, BUFSIZE) <= 0) {
58 perror("write to stdout failed");
59 exit(1);
60 }
61 offset = 0;
62 }
63 strncpy(buf + offset, string + string_offset, remaining);
64 offset += remaining;
65 }
66
67 static void print_cursor(sd_journal *j) {
68 int r;
69 char *cursor = NULL;
70 r = sd_journal_get_cursor(j, &cursor);
71 if (r < 0) {
72 fprintf(stderr, "Failed to get cursor: %s\n", strerror(-r));
73 exit(1);
74 }
75 print_to_buf(cursor, strlen(cursor));
76 print_to_buf("\n", 1);
77 free(cursor);
78 }
79
80 static void print_first_cursor(sd_journal *j) {
81 static bool printed_first_cursor = false;
82 if (!printed_first_cursor) {
83 print_cursor(j);
84 printed_first_cursor = true;
85 }
86 }
87
88 static void print_reboot(sd_journal *j) {
89 const char *d;
90 size_t l;
91 int r = sd_journal_get_data(j, "_BOOT_ID", (const void **)&d, &l);
92 if (r < 0) {
93 fprintf(stderr, "Failed %s\n", strerror(-r));
94 return;
95 }
96
97 // remove '_BOOT_ID='
98 d += 9;
99 l -= 9;
100
101 static char bootid[32];
102 if (bootid[0] != '\0') { // we have some bootid
103 if (strncmp(bootid, d, l)) { // a new bootid found
104 strncpy(bootid, d, l);
105 print_to_buf("-- Reboot --\n", 13);
106 }
107 } else {
108 strncpy(bootid, d, l);
109 }
110 }
111
112 static void print_timestamp(sd_journal *j) {
113 uint64_t timestamp = get_timestamp(j);
114 if (timestamp == (uint64_t)-1) {
115 return;
116 }
117
118 static uint64_t last_timestamp;
119 static char timestring[16];
120 if (timestamp >= (last_timestamp+(1000*1000))) {
121 timestamp = timestamp / (1000*1000); // usec to sec
122 struct tm time;
123 localtime_r((time_t *)&timestamp, &time);
124 strftime(timestring, 16, "%b %d %T", &time);
125 last_timestamp = timestamp;
126 }
127
128 print_to_buf(timestring, 15);
129 }
130
131 static void print_pid(sd_journal *j) {
132 const char *d;
133 size_t l;
134 int r = sd_journal_get_data(j, "_PID", (const void **)&d, &l);
135 if (r < 0) {
136 // we sometimes have no pid, e.g., kernel messages
137 return;
138 }
139
140 // remove '_PID='
141 d += 5;
142 l -= 5;
143
144 print_to_buf("[", 1);
145 print_to_buf(d, l);
146 print_to_buf("]", 1);
147 }
148
149 static bool print_field(sd_journal *j, const char *field) {
150 const char *d;
151 size_t l;
152 int r = sd_journal_get_data(j, field, (const void **)&d, &l);
153 if (r < 0) {
154 // some fields do not exists
155 return false;
156 }
157
158 size_t fieldlen = strlen(field)+1;
159 d += fieldlen;
160 l -= fieldlen;
161 print_to_buf(d, l);
162 return true;
163 }
164
165
166 static void print_line(sd_journal *j) {
167 print_reboot(j);
168 print_timestamp(j);
169 print_to_buf(" ", 1);
170 print_field(j, "_HOSTNAME");
171 print_to_buf(" ", 1);
172 if (!print_field(j, "SYSLOG_IDENTIFIER") &&
173 !print_field(j, "_COMM")) {
174 print_to_buf("unknown", strlen("unknown") - 1);
175 }
176 print_pid(j);
177 print_to_buf(": ", 2);
178 print_field(j, "MESSAGE");
179 print_to_buf("\n", 1);
180 }
181
182 static char *progname;
183
184 _Noreturn static void usage(char *error) {
185 if (error) {
186 fprintf(stderr, "ERROR: %s\n", error);
187 }
188 fprintf(stderr, "usage: %s [OPTIONS]\n", progname);
189 fprintf(stderr,
190 " -b <timestamp>\tbegin at this UNIX epoch based timestamp\n"
191 " -e <timestamp>\tend at this UNIX epoch based timestamp\n"
192 " -d <directory>\tpath to a journal directory\n"
193 " -n <integer>\t\tprint the last number entries logged\n"
194 " -f <cursor>\t\tprint from this cursor\n"
195 " -t <cursor>\t\tprint to this cursor\n"
196 " -h \t\t\tthis help\n"
197 "\n"
198 "Passing no range option will dump all the available journal\n"
199 "Giving a range conflicts with -n\n"
200 "-b and -f conflict\n"
201 "-e and -t conflict\n");
202 exit(error ? 1 : 0);
203 }
204
205 static uint64_t arg_to_uint64(const char *argument) {
206 errno = 0;
207 char * end;
208 uint64_t value = strtoull(argument, &end, 10);
209 if (errno != 0 || *end != '\0') {
210 fprintf(stderr, "%s is not a valid integer number\n", argument);
211 exit(1);
212 }
213
214 return value;
215 }
216
217
218 int main(int argc, char *argv[]) {
219 uint64_t number = 0;
220 const char *directory = NULL;
221 const char *startcursor = NULL;
222 const char *endcursor = NULL;
223 uint64_t begin = 0;
224 uint64_t end = 0;
225 char c;
226
227 progname = argv[0];
228
229 while ((c = (char)getopt (argc, argv, "b:e:d:n:f:t:h")) != -1) {
230 switch (c) {
231 case 'b':
232 begin = arg_to_uint64(optarg);
233 begin = begin * 1000 * 1000; // µs
234 break;
235 case 'e':
236 end = arg_to_uint64(optarg);
237 end = end * 1000 * 1000; // µs
238 break;
239 case 'd':
240 directory = optarg;
241 break;
242 case 'n':
243 number = arg_to_uint64(optarg);
244 break;
245 case 'f':
246 startcursor = optarg;
247 break;
248 case 't':
249 endcursor = optarg;
250 break;
251 case 'h':
252 usage(NULL);
253 case '?':
254 default:
255 usage("invalid option or missing argument");
256 }
257 }
258
259 if (number && (begin || startcursor)) {
260 usage("-n conflicts with -b and/or -f");
261 }
262
263 if (begin && startcursor) {
264 usage("-b and -f conflict");
265 }
266
267 if (end && endcursor) {
268 usage("-e and -t conflict");
269 }
270
271 if (argc > optind) {
272 usage("unkown, or to many arguments");
273 }
274
275 // to prevent calling it everytime we generate a timestamp
276 tzset();
277
278 int r;
279 sd_journal *j;
280 if (directory == NULL) {
281 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
282 } else {
283 r = sd_journal_open_directory(&j, directory, 0);
284 }
285
286 if (r < 0) {
287 fprintf(stderr, "Failed to open journal: %s\n", strerror(-r));
288 return 1;
289 }
290
291 // if we want to print the last x entries, seek to cursor or end,
292 // then x entries back, print the cursor and finally print the
293 // entries until end or cursor
294 if (number) {
295 if (end) {
296 r = sd_journal_seek_realtime_usec(j, end);
297 } else if (endcursor != NULL) {
298 r = sd_journal_seek_cursor(j, endcursor);
299 number++;
300 } else {
301 r = sd_journal_seek_tail(j);
302 }
303
304 if (r < 0) {
305 fprintf(stderr, "Failed to seek to end/cursor: %s\n", strerror(-r));
306 exit(1);
307 }
308
309 // seek back number entries and print cursor
310 r = sd_journal_previous_skip(j, number + 1);
311 if (r < 0) {
312 fprintf(stderr, "Failed to seek back: %s\n", strerror(-r));
313 exit(1);
314 }
315 } else {
316 if (begin) {
317 r = sd_journal_seek_realtime_usec(j, begin);
318 } else if (startcursor) {
319 r = sd_journal_seek_cursor(j, startcursor);
320 } else {
321 r = sd_journal_seek_head(j);
322 }
323
324 if (r < 0) {
325 fprintf(stderr, "Failed to seek to begin/cursor: %s\n", strerror(-r));
326 exit(1);
327 }
328
329 // if we have a start cursor, we want to skip the first entry
330 if (startcursor) {
331 r = sd_journal_next(j);
332 if (r < 0) {
333 fprintf(stderr, "Failed to seek to begin/cursor: %s\n", strerror(-r));
334 exit(1);
335 }
336 print_first_cursor(j);
337 }
338 }
339
340
341 while ((r = sd_journal_next(j)) > 0 && (end == 0 || get_timestamp(j) < end)) {
342 print_first_cursor(j);
343 if (endcursor != NULL && sd_journal_test_cursor(j, endcursor)) {
344 break;
345 }
346 print_line(j);
347 }
348
349 // print optional reboot
350 print_reboot(j);
351
352 // print final cursor
353 print_cursor(j);
354 sd_journal_close(j);
355
356 // print remaining buffer
357 if (write (1, buf, offset) <= 0) {
358 perror("write to stdout failed");
359 return 1;
360 }
361
362 return 0;
363 }
364