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