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