]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/sd-journal.c
journal: when the same entry is in two files, skip over them in sync
[thirdparty/systemd.git] / src / journal / sd-journal.c
CommitLineData
87d2c1ff
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2011 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
87d2c1ff 22#include <errno.h>
87d2c1ff 23#include <fcntl.h>
3fbf9cbb 24#include <stddef.h>
87d2c1ff
LP
25
26#include "sd-journal.h"
27#include "journal-def.h"
cec736d2 28#include "journal-file.h"
260a2be4 29#include "hashmap.h"
cec736d2 30#include "list.h"
87d2c1ff 31
cec736d2 32typedef struct Match Match;
87d2c1ff 33
cec736d2
LP
34struct Match {
35 char *data;
36 size_t size;
37 uint64_t hash;
87d2c1ff 38
cec736d2
LP
39 LIST_FIELDS(Match, matches);
40};
87d2c1ff 41
87d2c1ff 42struct sd_journal {
260a2be4 43 Hashmap *files;
87d2c1ff 44
cec736d2 45 JournalFile *current_file;
87d2c1ff 46
cec736d2
LP
47 LIST_HEAD(Match, matches);
48};
87d2c1ff 49
cec736d2
LP
50int sd_journal_add_match(sd_journal *j, const char *field, const void *data, size_t size) {
51 Match *m;
52 char *e;
87d2c1ff 53
cec736d2
LP
54 assert(j);
55 assert(field);
56 assert(data || size == 0);
87d2c1ff 57
cec736d2
LP
58 m = new0(Match, 1);
59 if (!m)
60 return -ENOMEM;
87d2c1ff 61
cec736d2
LP
62 m->size = strlen(field) + 1 + size;
63 m->data = malloc(m->size);
64 if (!m->data) {
65 free(m);
66 return -ENOMEM;
87d2c1ff
LP
67 }
68
cec736d2
LP
69 e = stpcpy(m->data, field);
70 *(e++) = '=';
71 memcpy(e, data, size);
87d2c1ff 72
cec736d2 73 LIST_PREPEND(Match, matches, j->matches, m);
87d2c1ff
LP
74 return 0;
75}
76
cec736d2
LP
77void sd_journal_flush_matches(sd_journal *j) {
78 assert(j);
87d2c1ff 79
cec736d2
LP
80 while (j->matches) {
81 Match *m = j->matches;
87d2c1ff 82
cec736d2
LP
83 LIST_REMOVE(Match, matches, j->matches, m);
84 free(m->data);
85 free(m);
87d2c1ff 86 }
87d2c1ff
LP
87}
88
cec736d2 89static int compare_order(JournalFile *af, Object *ao, uint64_t ap,
ae2cc8ef 90 JournalFile *bf, Object *bo, uint64_t bp) {
87d2c1ff 91
cec736d2 92 uint64_t a, b;
87d2c1ff 93
ae2cc8ef
LP
94 /* We operate on two different files here, hence we can access
95 * two objects at the same time, which we normally can't */
96
cec736d2 97 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
87d2c1ff 98
cec736d2
LP
99 /* If this is from the same seqnum source, compare
100 * seqnums */
101 a = le64toh(ao->entry.seqnum);
102 b = le64toh(bo->entry.seqnum);
87d2c1ff 103
ae2cc8ef
LP
104 if (a < b)
105 return -1;
106 if (a > b)
107 return 1;
108 }
87d2c1ff 109
ae2cc8ef 110 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
87d2c1ff 111
cec736d2
LP
112 /* If the boot id matches compare monotonic time */
113 a = le64toh(ao->entry.monotonic);
114 b = le64toh(bo->entry.monotonic);
87d2c1ff 115
ae2cc8ef
LP
116 if (a < b)
117 return -1;
118 if (a > b)
119 return 1;
87d2c1ff
LP
120 }
121
ae2cc8ef
LP
122 /* Otherwise compare UTC time */
123 a = le64toh(ao->entry.realtime);
124 b = le64toh(ao->entry.realtime);
125
126 if (a < b)
127 return -1;
128 if (a > b)
129 return 1;
130
131 /* Finally, compare by contents */
132 a = le64toh(ao->entry.xor_hash);
133 b = le64toh(ao->entry.xor_hash);
134
135 if (a < b)
136 return -1;
137 if (a > b)
138 return 1;
139
140 return 0;
87d2c1ff
LP
141}
142
cec736d2
LP
143int sd_journal_next(sd_journal *j) {
144 JournalFile *f, *new_current = NULL;
145 Iterator i;
87d2c1ff 146 int r;
cec736d2
LP
147 uint64_t new_offset = 0;
148 Object *new_entry = NULL;
87d2c1ff 149
cec736d2 150 assert(j);
87d2c1ff 151
cec736d2
LP
152 HASHMAP_FOREACH(f, j->files, i) {
153 Object *o;
154 uint64_t p;
87d2c1ff 155
cec736d2
LP
156 if (f->current_offset > 0) {
157 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
158 if (r < 0)
159 return r;
160 } else
161 o = NULL;
87d2c1ff 162
cec736d2 163 r = journal_file_next_entry(f, o, &o, &p);
87d2c1ff
LP
164 if (r < 0)
165 return r;
cec736d2
LP
166 else if (r == 0)
167 continue;
87d2c1ff 168
ae2cc8ef
LP
169 if (!new_current ||
170 compare_order(new_current, new_entry, new_offset, f, o, p) > 0) {
cec736d2
LP
171 new_current = f;
172 new_entry = o;
173 new_offset = p;
87d2c1ff 174 }
87d2c1ff
LP
175 }
176
cec736d2
LP
177 if (new_current) {
178 j->current_file = new_current;
3fbf9cbb
LP
179 j->current_file->current_offset = new_offset;
180 j->current_file->current_field = 0;
ae2cc8ef
LP
181
182 /* Skip over any identical entries in the other files too */
183
184 HASHMAP_FOREACH(f, j->files, i) {
185 Object *o;
186 uint64_t p;
187
188 if (j->current_file == f)
189 continue;
190
191 if (f->current_offset > 0) {
192 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
193 if (r < 0)
194 return r;
195 } else
196 o = NULL;
197
198 r = journal_file_next_entry(f, o, &o, &p);
199 if (r < 0)
200 return r;
201 else if (r == 0)
202 continue;
203
204 if (compare_order(new_current, new_entry, new_offset, f, o, p) == 0) {
205 f->current_offset = p;
206 f->current_field = 0;
207 }
208 }
209
cec736d2 210 return 1;
87d2c1ff
LP
211 }
212
87d2c1ff
LP
213 return 0;
214}
215
cec736d2
LP
216int sd_journal_previous(sd_journal *j) {
217 JournalFile *f, *new_current = NULL;
218 Iterator i;
87d2c1ff 219 int r;
cec736d2
LP
220 uint64_t new_offset = 0;
221 Object *new_entry = NULL;
87d2c1ff 222
cec736d2 223 assert(j);
87d2c1ff 224
cec736d2 225 HASHMAP_FOREACH(f, j->files, i) {
dad50316 226 Object *o;
cec736d2 227 uint64_t p;
87d2c1ff 228
cec736d2
LP
229 if (f->current_offset > 0) {
230 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
231 if (r < 0)
232 return r;
233 } else
234 o = NULL;
87d2c1ff 235
cec736d2 236 r = journal_file_prev_entry(f, o, &o, &p);
87d2c1ff
LP
237 if (r < 0)
238 return r;
cec736d2
LP
239 else if (r == 0)
240 continue;
87d2c1ff 241
cec736d2
LP
242 if (!new_current || compare_order(new_current, new_entry, new_offset, f, o, p) > 0) {
243 new_current = f;
244 new_entry = o;
245 new_offset = p;
246 }
87d2c1ff
LP
247 }
248
cec736d2
LP
249 if (new_current) {
250 j->current_file = new_current;
3fbf9cbb
LP
251 j->current_file->current_offset = new_offset;
252 j->current_file->current_field = 0;
cec736d2 253 return 1;
87d2c1ff
LP
254 }
255
256 return 0;
257}
258
3fbf9cbb 259int sd_journal_get_cursor(sd_journal *j, char **cursor) {
cec736d2 260 Object *o;
87d2c1ff 261 int r;
3fbf9cbb 262 char bid[33], sid[33];
87d2c1ff 263
cec736d2
LP
264 assert(j);
265 assert(cursor);
87d2c1ff 266
3fbf9cbb
LP
267 if (!j->current_file || j->current_file->current_offset <= 0)
268 return -EADDRNOTAVAIL;
87d2c1ff 269
cec736d2 270 r = journal_file_move_to_object(j->current_file, j->current_file->current_offset, OBJECT_ENTRY, &o);
87d2c1ff
LP
271 if (r < 0)
272 return r;
273
3fbf9cbb
LP
274 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
275 sd_id128_to_string(o->entry.boot_id, bid);
87d2c1ff 276
3fbf9cbb
LP
277 if (asprintf(cursor,
278 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
279 sid, (unsigned long long) le64toh(o->entry.seqnum),
280 bid, (unsigned long long) le64toh(o->entry.monotonic),
281 (unsigned long long) le64toh(o->entry.realtime),
282 (unsigned long long) le64toh(o->entry.xor_hash),
283 file_name_from_path(j->current_file->path)) < 0)
284 return -ENOMEM;
87d2c1ff
LP
285
286 return 1;
287}
288
3fbf9cbb 289int sd_journal_set_cursor(sd_journal *j, const char *cursor) {
cec736d2 290 return -EINVAL;
87d2c1ff
LP
291}
292
3fbf9cbb
LP
293static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
294 char *fn;
295 int r;
296 JournalFile *f;
297
298 assert(j);
299 assert(prefix);
300 assert(filename);
301
302 if (dir)
303 fn = join(prefix, "/", dir, "/", filename, NULL);
304 else
305 fn = join(prefix, "/", filename, NULL);
306
307 if (!fn)
308 return -ENOMEM;
309
310 r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
311 free(fn);
312
313 if (r < 0) {
314 if (errno == ENOENT)
315 return 0;
316
317 return r;
318 }
319
320 r = hashmap_put(j->files, f->path, f);
321 if (r < 0) {
322 journal_file_close(f);
323 return r;
324 }
325
326 return 0;
327}
328
329static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
330 char *fn;
331 int r;
332 DIR *d;
333
334 assert(j);
335 assert(prefix);
336 assert(dir);
337
338 fn = join(prefix, "/", dir, NULL);
339 if (!fn)
340 return -ENOMEM;
341
342 d = opendir(fn);
343 free(fn);
344
345 if (!d) {
346 if (errno == ENOENT)
347 return 0;
348
349 return -errno;
350 }
351
352 for (;;) {
353 struct dirent buf, *de;
354
355 r = readdir_r(d, &buf, &de);
356 if (r != 0 || !de)
357 break;
358
359 if (!dirent_is_file_with_suffix(de, ".journal"))
360 continue;
361
362 r = add_file(j, prefix, dir, de->d_name);
363 if (r < 0)
364 log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
365 }
366
367 closedir(d);
368
369 return 0;
370}
371
87d2c1ff
LP
372int sd_journal_open(sd_journal **ret) {
373 sd_journal *j;
87d2c1ff 374 const char *p;
87d2c1ff
LP
375 const char search_paths[] =
376 "/run/log/journal\0"
377 "/var/log/journal\0";
3fbf9cbb 378 int r;
87d2c1ff
LP
379
380 assert(ret);
381
382 j = new0(sd_journal, 1);
383 if (!j)
384 return -ENOMEM;
385
260a2be4 386 j->files = hashmap_new(string_hash_func, string_compare_func);
3fbf9cbb
LP
387 if (!j->files) {
388 r = -ENOMEM;
260a2be4 389 goto fail;
3fbf9cbb
LP
390 }
391
392 /* We ignore most errors here, since the idea is to only open
393 * what's actually accessible, and ignore the rest. */
260a2be4 394
87d2c1ff
LP
395 NULSTR_FOREACH(p, search_paths) {
396 DIR *d;
397
398 d = opendir(p);
399 if (!d) {
3fbf9cbb
LP
400 if (errno != ENOENT)
401 log_debug("Failed to open %s: %m", p);
87d2c1ff
LP
402 continue;
403 }
404
405 for (;;) {
406 struct dirent buf, *de;
3fbf9cbb 407 sd_id128_t id;
87d2c1ff 408
3fbf9cbb
LP
409 r = readdir_r(d, &buf, &de);
410 if (r != 0 || !de)
87d2c1ff
LP
411 break;
412
3fbf9cbb
LP
413 if (dirent_is_file_with_suffix(de, ".journal")) {
414 r = add_file(j, p, NULL, de->d_name);
415 if (r < 0)
416 log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
87d2c1ff 417
3fbf9cbb
LP
418 } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
419 sd_id128_from_string(de->d_name, &id) >= 0) {
87d2c1ff 420
3fbf9cbb
LP
421 r = add_directory(j, p, de->d_name);
422 if (r < 0)
423 log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
260a2be4
LP
424 }
425 }
3fbf9cbb
LP
426
427 closedir(d);
87d2c1ff
LP
428 }
429
430 *ret = j;
431 return 0;
432
433fail:
434 sd_journal_close(j);
435
436 return r;
437};
438
439void sd_journal_close(sd_journal *j) {
440 assert(j);
441
260a2be4
LP
442 if (j->files) {
443 JournalFile *f;
444
445 while ((f = hashmap_steal_first(j->files)))
446 journal_file_close(f);
447
448 hashmap_free(j->files);
449 }
87d2c1ff
LP
450
451 free(j);
452}
3fbf9cbb
LP
453
454int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
455 Object *o;
456 JournalFile *f;
457 int r;
458
459 assert(j);
460 assert(ret);
461
462 f = j->current_file;
463 if (!f)
464 return 0;
465
466 if (f->current_offset <= 0)
467 return 0;
468
469 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
470 if (r < 0)
471 return r;
472
473 *ret = le64toh(o->entry.realtime);
474 return 1;
475}
476
477int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret) {
478 Object *o;
479 JournalFile *f;
480 int r;
481 sd_id128_t id;
482
483 assert(j);
484 assert(ret);
485
486 f = j->current_file;
487 if (!f)
488 return 0;
489
490 if (f->current_offset <= 0)
491 return 0;
492
493 r = sd_id128_get_machine(&id);
494 if (r < 0)
495 return r;
496
497 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
498 if (r < 0)
499 return r;
500
501 if (!sd_id128_equal(id, o->entry.boot_id))
502 return 0;
503
504 *ret = le64toh(o->entry.monotonic);
505 return 1;
506
507}
508
509int sd_journal_get_field(sd_journal *j, const char *field, const void **data, size_t *size) {
510 JournalFile *f;
511 uint64_t i, n;
512 size_t field_length;
513 int r;
514 Object *o;
515
516 assert(j);
517 assert(field);
518 assert(data);
519 assert(size);
520
521 if (isempty(field) || strchr(field, '='))
522 return -EINVAL;
523
524 f = j->current_file;
525 if (!f)
526 return 0;
527
528 if (f->current_offset <= 0)
529 return 0;
530
531 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
532 if (r < 0)
533 return r;
534
535 field_length = strlen(field);
536
537 n = journal_file_entry_n_items(o);
538 for (i = 0; i < n; i++) {
539 uint64_t p, l;
540 size_t t;
541
542 p = le64toh(o->entry.items[i].object_offset);
543 r = journal_file_move_to_object(f, p, OBJECT_DATA, &o);
544 if (r < 0)
545 return r;
546
547 l = le64toh(o->object.size) - offsetof(Object, data.payload);
548
161e54f8
LP
549 if (l >= field_length+1 &&
550 memcmp(o->data.payload, field, field_length) == 0 &&
551 o->data.payload[field_length] == '=') {
3fbf9cbb 552
161e54f8 553 t = (size_t) l;
3fbf9cbb 554
161e54f8
LP
555 if ((uint64_t) t != l)
556 return -E2BIG;
3fbf9cbb 557
161e54f8
LP
558 *data = o->data.payload;
559 *size = t;
3fbf9cbb 560
161e54f8
LP
561 return 1;
562 }
3fbf9cbb 563
161e54f8
LP
564 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
565 if (r < 0)
566 return r;
3fbf9cbb
LP
567 }
568
569 return 0;
570}
571
572int sd_journal_iterate_fields(sd_journal *j, const void **data, size_t *size) {
573 JournalFile *f;
574 uint64_t p, l, n;
575 size_t t;
576 int r;
577 Object *o;
578
579 assert(j);
580 assert(data);
581 assert(size);
582
583 f = j->current_file;
584 if (!f)
585 return 0;
586
587 if (f->current_offset <= 0)
588 return 0;
589
590 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
591 if (r < 0)
592 return r;
593
594 n = journal_file_entry_n_items(o);
595 if (f->current_field >= n)
596 return 0;
597
598 p = le64toh(o->entry.items[f->current_field].object_offset);
599 r = journal_file_move_to_object(f, p, OBJECT_DATA, &o);
600 if (r < 0)
601 return r;
602
603 l = le64toh(o->object.size) - offsetof(Object, data.payload);
604 t = (size_t) l;
605
606 /* We can't read objects larger than 4G on a 32bit machine */
607 if ((uint64_t) t != l)
608 return -E2BIG;
609
610 *data = o->data.payload;
611 *size = t;
612
613 f->current_field ++;
614
615 return 1;
616}
c2373f84
LP
617
618int sd_journal_seek_head(sd_journal *j) {
619 assert(j);
620 return -EINVAL;
621}
622
623int sd_journal_seek_tail(sd_journal *j) {
624 assert(j);
625 return -EINVAL;
626}