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