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