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