]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/sd-journal.c
journal: properly implement matching with multiple matches
[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"
de7b95cd 31#include "lookup3.h"
87d2c1ff 32
cec736d2 33typedef struct Match Match;
87d2c1ff 34
cec736d2
LP
35struct Match {
36 char *data;
37 size_t size;
de7b95cd 38 uint64_t le_hash;
87d2c1ff 39
cec736d2
LP
40 LIST_FIELDS(Match, matches);
41};
87d2c1ff 42
87d2c1ff 43struct sd_journal {
260a2be4 44 Hashmap *files;
87d2c1ff 45
cec736d2 46 JournalFile *current_file;
7210bfb3 47 uint64_t current_field;
87d2c1ff 48
cec736d2 49 LIST_HEAD(Match, matches);
de7b95cd 50 unsigned n_matches;
cec736d2 51};
87d2c1ff 52
1cc101f1 53int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
cec736d2 54 Match *m;
87d2c1ff 55
cec736d2 56 assert(j);
1cc101f1
LP
57
58 if (size <= 0)
59 return -EINVAL;
60
61 assert(data);
87d2c1ff 62
cec736d2
LP
63 m = new0(Match, 1);
64 if (!m)
65 return -ENOMEM;
87d2c1ff 66
1cc101f1
LP
67 m->size = size;
68
cec736d2
LP
69 m->data = malloc(m->size);
70 if (!m->data) {
71 free(m);
72 return -ENOMEM;
87d2c1ff
LP
73 }
74
1cc101f1 75 memcpy(m->data, data, size);
de7b95cd 76 m->le_hash = hash64(m->data, size);
87d2c1ff 77
cec736d2 78 LIST_PREPEND(Match, matches, j->matches, m);
de7b95cd
LP
79 j->n_matches ++;
80
87d2c1ff
LP
81 return 0;
82}
83
cec736d2
LP
84void sd_journal_flush_matches(sd_journal *j) {
85 assert(j);
87d2c1ff 86
cec736d2
LP
87 while (j->matches) {
88 Match *m = j->matches;
87d2c1ff 89
cec736d2
LP
90 LIST_REMOVE(Match, matches, j->matches, m);
91 free(m->data);
92 free(m);
87d2c1ff 93 }
de7b95cd
LP
94
95 j->n_matches = 0;
87d2c1ff
LP
96}
97
cec736d2 98static int compare_order(JournalFile *af, Object *ao, uint64_t ap,
ae2cc8ef 99 JournalFile *bf, Object *bo, uint64_t bp) {
87d2c1ff 100
cec736d2 101 uint64_t a, b;
87d2c1ff 102
ae2cc8ef 103 /* We operate on two different files here, hence we can access
1cc101f1
LP
104 * two objects at the same time, which we normally can't.
105 *
106 * If contents and timestamps match, these entries are
107 * identical, even if the seqnum does not match */
108
109 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
110 ao->entry.monotonic == bo->entry.monotonic &&
111 ao->entry.realtime == bo->entry.realtime &&
112 ao->entry.xor_hash == bo->entry.xor_hash)
113 return 0;
ae2cc8ef 114
cec736d2 115 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
87d2c1ff 116
cec736d2
LP
117 /* If this is from the same seqnum source, compare
118 * seqnums */
119 a = le64toh(ao->entry.seqnum);
120 b = le64toh(bo->entry.seqnum);
87d2c1ff 121
ae2cc8ef
LP
122 if (a < b)
123 return -1;
124 if (a > b)
125 return 1;
1cc101f1
LP
126
127 /* Wow! This is weird, different data but the same
128 * seqnums? Something is borked, but let's make the
129 * best of it and compare by time. */
ae2cc8ef 130 }
87d2c1ff 131
ae2cc8ef 132 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
87d2c1ff 133
cec736d2
LP
134 /* If the boot id matches compare monotonic time */
135 a = le64toh(ao->entry.monotonic);
136 b = le64toh(bo->entry.monotonic);
87d2c1ff 137
ae2cc8ef
LP
138 if (a < b)
139 return -1;
140 if (a > b)
141 return 1;
87d2c1ff
LP
142 }
143
ae2cc8ef
LP
144 /* Otherwise compare UTC time */
145 a = le64toh(ao->entry.realtime);
146 b = le64toh(ao->entry.realtime);
147
148 if (a < b)
149 return -1;
150 if (a > b)
151 return 1;
152
153 /* Finally, compare by contents */
154 a = le64toh(ao->entry.xor_hash);
155 b = le64toh(ao->entry.xor_hash);
156
157 if (a < b)
158 return -1;
159 if (a > b)
160 return 1;
161
162 return 0;
87d2c1ff
LP
163}
164
de7b95cd
LP
165static int move_to_next_with_matches(sd_journal *j, JournalFile *f, Object **o, uint64_t *p) {
166 int r;
167 uint64_t cp;
168 Object *c;
169
170 assert(j);
171 assert(f);
172 assert(o);
173 assert(p);
174
175 if (!j->matches) {
176 /* No matches is easy, just go on to the next entry */
177
178 if (f->current_offset > 0) {
179 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &c);
180 if (r < 0)
181 return r;
182 } else
183 c = NULL;
184
185 return journal_file_next_entry(f, c, o, p);
186 }
187
188 /* So there are matches we have to adhere to, let's find the
189 * first entry that matches all of them */
190
191 if (f->current_offset > 0)
192 cp = f->current_offset;
193 else {
194 r = journal_file_find_first_entry(f, j->matches->data, j->matches->size, &c, &cp);
195 if (r <= 0)
196 return r;
197
198 /* We can shortcut this if there's only one match */
199 if (j->n_matches == 1) {
200 *o = c;
201 *p = cp;
202 return r;
203 }
204 }
205
206 for (;;) {
207 uint64_t np, n;
208 bool found;
209 Match *m;
210
211 r = journal_file_move_to_object(f, cp, OBJECT_ENTRY, &c);
212 if (r < 0)
213 return r;
214
215 n = journal_file_entry_n_items(c);
216
217 /* Make sure we don't match the entry we are starting
218 * from. */
219 found = f->current_offset != cp;
220
221 np = 0;
222 LIST_FOREACH(matches, m, j->matches) {
223 uint64_t q, k;
224
225 for (k = 0; k < n; k++)
226 if (c->entry.items[k].hash == m->le_hash)
227 break;
228
229 if (k >= n) {
230 /* Hmm, didn't find any field that matched, so ignore
231 * this match. Go on with next match */
232
233 found = false;
234 continue;
235 }
236
237 /* Hmm, so, this field matched, let's remember
238 * where we'd have to try next, in case the other
239 * matches are not OK */
240 q = le64toh(c->entry.items[k].next_entry_offset);
241 if (q > np)
242 np = q;
243 }
244
245 /* Did this entry match against all matches? */
246 if (found) {
247 *o = c;
248 *p = cp;
249 return 1;
250 }
251
252 /* Did we find a subsequent entry? */
253 if (np == 0)
254 return 0;
255
256 /* Hmm, ok, this entry only matched partially, so
257 * let's try another one */
258 cp = np;
259 }
260}
261
cec736d2
LP
262int sd_journal_next(sd_journal *j) {
263 JournalFile *f, *new_current = NULL;
264 Iterator i;
87d2c1ff 265 int r;
cec736d2
LP
266 uint64_t new_offset = 0;
267 Object *new_entry = NULL;
87d2c1ff 268
cec736d2 269 assert(j);
87d2c1ff 270
cec736d2
LP
271 HASHMAP_FOREACH(f, j->files, i) {
272 Object *o;
273 uint64_t p;
87d2c1ff 274
de7b95cd 275 r = move_to_next_with_matches(j, f, &o, &p);
87d2c1ff
LP
276 if (r < 0)
277 return r;
cec736d2
LP
278 else if (r == 0)
279 continue;
87d2c1ff 280
ae2cc8ef
LP
281 if (!new_current ||
282 compare_order(new_current, new_entry, new_offset, f, o, p) > 0) {
cec736d2
LP
283 new_current = f;
284 new_entry = o;
285 new_offset = p;
87d2c1ff 286 }
87d2c1ff
LP
287 }
288
cec736d2
LP
289 if (new_current) {
290 j->current_file = new_current;
3fbf9cbb 291 j->current_file->current_offset = new_offset;
7210bfb3 292 j->current_field = 0;
ae2cc8ef
LP
293
294 /* Skip over any identical entries in the other files too */
295
296 HASHMAP_FOREACH(f, j->files, i) {
297 Object *o;
298 uint64_t p;
299
300 if (j->current_file == f)
301 continue;
302
de7b95cd 303 r = move_to_next_with_matches(j, f, &o, &p);
ae2cc8ef
LP
304 if (r < 0)
305 return r;
306 else if (r == 0)
307 continue;
308
7210bfb3 309 if (compare_order(new_current, new_entry, new_offset, f, o, p) == 0)
ae2cc8ef 310 f->current_offset = p;
ae2cc8ef
LP
311 }
312
cec736d2 313 return 1;
87d2c1ff
LP
314 }
315
87d2c1ff
LP
316 return 0;
317}
318
cec736d2
LP
319int sd_journal_previous(sd_journal *j) {
320 JournalFile *f, *new_current = NULL;
321 Iterator i;
87d2c1ff 322 int r;
cec736d2
LP
323 uint64_t new_offset = 0;
324 Object *new_entry = NULL;
87d2c1ff 325
cec736d2 326 assert(j);
87d2c1ff 327
cec736d2 328 HASHMAP_FOREACH(f, j->files, i) {
dad50316 329 Object *o;
cec736d2 330 uint64_t p;
87d2c1ff 331
cec736d2
LP
332 if (f->current_offset > 0) {
333 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
334 if (r < 0)
335 return r;
336 } else
337 o = NULL;
87d2c1ff 338
cec736d2 339 r = journal_file_prev_entry(f, o, &o, &p);
87d2c1ff
LP
340 if (r < 0)
341 return r;
cec736d2
LP
342 else if (r == 0)
343 continue;
87d2c1ff 344
cec736d2
LP
345 if (!new_current || compare_order(new_current, new_entry, new_offset, f, o, p) > 0) {
346 new_current = f;
347 new_entry = o;
348 new_offset = p;
349 }
87d2c1ff
LP
350 }
351
cec736d2
LP
352 if (new_current) {
353 j->current_file = new_current;
3fbf9cbb 354 j->current_file->current_offset = new_offset;
7210bfb3
LP
355 j->current_field = 0;
356
357 /* Skip over any identical entries in the other files too */
358
359 HASHMAP_FOREACH(f, j->files, i) {
360 Object *o;
361 uint64_t p;
362
363 if (j->current_file == f)
364 continue;
365
366 if (f->current_offset > 0) {
367 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
368 if (r < 0)
369 return r;
370 } else
371 o = NULL;
372
373 r = journal_file_prev_entry(f, o, &o, &p);
374 if (r < 0)
375 return r;
376 else if (r == 0)
377 continue;
378
379 if (compare_order(new_current, new_entry, new_offset, f, o, p) == 0)
380 f->current_offset = p;
381 }
382
cec736d2 383 return 1;
87d2c1ff
LP
384 }
385
386 return 0;
387}
388
3fbf9cbb 389int sd_journal_get_cursor(sd_journal *j, char **cursor) {
cec736d2 390 Object *o;
87d2c1ff 391 int r;
3fbf9cbb 392 char bid[33], sid[33];
87d2c1ff 393
cec736d2
LP
394 assert(j);
395 assert(cursor);
87d2c1ff 396
3fbf9cbb
LP
397 if (!j->current_file || j->current_file->current_offset <= 0)
398 return -EADDRNOTAVAIL;
87d2c1ff 399
cec736d2 400 r = journal_file_move_to_object(j->current_file, j->current_file->current_offset, OBJECT_ENTRY, &o);
87d2c1ff
LP
401 if (r < 0)
402 return r;
403
3fbf9cbb
LP
404 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
405 sd_id128_to_string(o->entry.boot_id, bid);
87d2c1ff 406
3fbf9cbb
LP
407 if (asprintf(cursor,
408 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
409 sid, (unsigned long long) le64toh(o->entry.seqnum),
410 bid, (unsigned long long) le64toh(o->entry.monotonic),
411 (unsigned long long) le64toh(o->entry.realtime),
412 (unsigned long long) le64toh(o->entry.xor_hash),
413 file_name_from_path(j->current_file->path)) < 0)
414 return -ENOMEM;
87d2c1ff
LP
415
416 return 1;
417}
418
3fbf9cbb 419int sd_journal_set_cursor(sd_journal *j, const char *cursor) {
cec736d2 420 return -EINVAL;
87d2c1ff
LP
421}
422
3fbf9cbb
LP
423static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
424 char *fn;
425 int r;
426 JournalFile *f;
427
428 assert(j);
429 assert(prefix);
430 assert(filename);
431
432 if (dir)
433 fn = join(prefix, "/", dir, "/", filename, NULL);
434 else
435 fn = join(prefix, "/", filename, NULL);
436
437 if (!fn)
438 return -ENOMEM;
439
440 r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
441 free(fn);
442
443 if (r < 0) {
444 if (errno == ENOENT)
445 return 0;
446
447 return r;
448 }
449
450 r = hashmap_put(j->files, f->path, f);
451 if (r < 0) {
452 journal_file_close(f);
453 return r;
454 }
455
456 return 0;
457}
458
459static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
460 char *fn;
461 int r;
462 DIR *d;
463
464 assert(j);
465 assert(prefix);
466 assert(dir);
467
468 fn = join(prefix, "/", dir, NULL);
469 if (!fn)
470 return -ENOMEM;
471
472 d = opendir(fn);
473 free(fn);
474
475 if (!d) {
476 if (errno == ENOENT)
477 return 0;
478
479 return -errno;
480 }
481
482 for (;;) {
483 struct dirent buf, *de;
484
485 r = readdir_r(d, &buf, &de);
486 if (r != 0 || !de)
487 break;
488
489 if (!dirent_is_file_with_suffix(de, ".journal"))
490 continue;
491
492 r = add_file(j, prefix, dir, de->d_name);
493 if (r < 0)
494 log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
495 }
496
497 closedir(d);
498
499 return 0;
500}
501
87d2c1ff
LP
502int sd_journal_open(sd_journal **ret) {
503 sd_journal *j;
87d2c1ff 504 const char *p;
87d2c1ff
LP
505 const char search_paths[] =
506 "/run/log/journal\0"
507 "/var/log/journal\0";
3fbf9cbb 508 int r;
87d2c1ff
LP
509
510 assert(ret);
511
512 j = new0(sd_journal, 1);
513 if (!j)
514 return -ENOMEM;
515
260a2be4 516 j->files = hashmap_new(string_hash_func, string_compare_func);
3fbf9cbb
LP
517 if (!j->files) {
518 r = -ENOMEM;
260a2be4 519 goto fail;
3fbf9cbb
LP
520 }
521
522 /* We ignore most errors here, since the idea is to only open
523 * what's actually accessible, and ignore the rest. */
260a2be4 524
87d2c1ff
LP
525 NULSTR_FOREACH(p, search_paths) {
526 DIR *d;
527
528 d = opendir(p);
529 if (!d) {
3fbf9cbb
LP
530 if (errno != ENOENT)
531 log_debug("Failed to open %s: %m", p);
87d2c1ff
LP
532 continue;
533 }
534
535 for (;;) {
536 struct dirent buf, *de;
3fbf9cbb 537 sd_id128_t id;
87d2c1ff 538
3fbf9cbb
LP
539 r = readdir_r(d, &buf, &de);
540 if (r != 0 || !de)
87d2c1ff
LP
541 break;
542
3fbf9cbb
LP
543 if (dirent_is_file_with_suffix(de, ".journal")) {
544 r = add_file(j, p, NULL, de->d_name);
545 if (r < 0)
546 log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
87d2c1ff 547
3fbf9cbb
LP
548 } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
549 sd_id128_from_string(de->d_name, &id) >= 0) {
87d2c1ff 550
3fbf9cbb
LP
551 r = add_directory(j, p, de->d_name);
552 if (r < 0)
553 log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
260a2be4
LP
554 }
555 }
3fbf9cbb
LP
556
557 closedir(d);
87d2c1ff
LP
558 }
559
560 *ret = j;
561 return 0;
562
563fail:
564 sd_journal_close(j);
565
566 return r;
567};
568
569void sd_journal_close(sd_journal *j) {
570 assert(j);
571
260a2be4
LP
572 if (j->files) {
573 JournalFile *f;
574
575 while ((f = hashmap_steal_first(j->files)))
576 journal_file_close(f);
577
578 hashmap_free(j->files);
579 }
87d2c1ff 580
1cc101f1
LP
581 sd_journal_flush_matches(j);
582
87d2c1ff
LP
583 free(j);
584}
3fbf9cbb
LP
585
586int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
587 Object *o;
588 JournalFile *f;
589 int r;
590
591 assert(j);
592 assert(ret);
593
594 f = j->current_file;
595 if (!f)
596 return 0;
597
598 if (f->current_offset <= 0)
599 return 0;
600
601 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
602 if (r < 0)
603 return r;
604
605 *ret = le64toh(o->entry.realtime);
606 return 1;
607}
608
609int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret) {
610 Object *o;
611 JournalFile *f;
612 int r;
613 sd_id128_t id;
614
615 assert(j);
616 assert(ret);
617
618 f = j->current_file;
619 if (!f)
620 return 0;
621
622 if (f->current_offset <= 0)
623 return 0;
624
de7b95cd 625 r = sd_id128_get_boot(&id);
3fbf9cbb
LP
626 if (r < 0)
627 return r;
628
629 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
630 if (r < 0)
631 return r;
632
633 if (!sd_id128_equal(id, o->entry.boot_id))
634 return 0;
635
636 *ret = le64toh(o->entry.monotonic);
637 return 1;
638
639}
640
641int sd_journal_get_field(sd_journal *j, const char *field, const void **data, size_t *size) {
642 JournalFile *f;
643 uint64_t i, n;
644 size_t field_length;
645 int r;
646 Object *o;
647
648 assert(j);
649 assert(field);
650 assert(data);
651 assert(size);
652
653 if (isempty(field) || strchr(field, '='))
654 return -EINVAL;
655
656 f = j->current_file;
657 if (!f)
658 return 0;
659
660 if (f->current_offset <= 0)
661 return 0;
662
663 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
664 if (r < 0)
665 return r;
666
667 field_length = strlen(field);
668
669 n = journal_file_entry_n_items(o);
670 for (i = 0; i < n; i++) {
de7b95cd 671 uint64_t p, l, h;
3fbf9cbb
LP
672 size_t t;
673
674 p = le64toh(o->entry.items[i].object_offset);
de7b95cd 675 h = o->entry.items[j->current_field].hash;
3fbf9cbb
LP
676 r = journal_file_move_to_object(f, p, OBJECT_DATA, &o);
677 if (r < 0)
678 return r;
679
de7b95cd
LP
680 if (h != o->data.hash)
681 return -EBADMSG;
682
3fbf9cbb
LP
683 l = le64toh(o->object.size) - offsetof(Object, data.payload);
684
161e54f8
LP
685 if (l >= field_length+1 &&
686 memcmp(o->data.payload, field, field_length) == 0 &&
687 o->data.payload[field_length] == '=') {
3fbf9cbb 688
161e54f8 689 t = (size_t) l;
3fbf9cbb 690
161e54f8
LP
691 if ((uint64_t) t != l)
692 return -E2BIG;
3fbf9cbb 693
161e54f8
LP
694 *data = o->data.payload;
695 *size = t;
3fbf9cbb 696
161e54f8
LP
697 return 1;
698 }
3fbf9cbb 699
161e54f8
LP
700 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
701 if (r < 0)
702 return r;
3fbf9cbb
LP
703 }
704
705 return 0;
706}
707
708int sd_journal_iterate_fields(sd_journal *j, const void **data, size_t *size) {
709 JournalFile *f;
de7b95cd 710 uint64_t p, l, n, h;
3fbf9cbb
LP
711 size_t t;
712 int r;
713 Object *o;
714
715 assert(j);
716 assert(data);
717 assert(size);
718
719 f = j->current_file;
720 if (!f)
721 return 0;
722
723 if (f->current_offset <= 0)
724 return 0;
725
726 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
727 if (r < 0)
728 return r;
729
730 n = journal_file_entry_n_items(o);
7210bfb3 731 if (j->current_field >= n)
3fbf9cbb
LP
732 return 0;
733
7210bfb3 734 p = le64toh(o->entry.items[j->current_field].object_offset);
de7b95cd 735 h = o->entry.items[j->current_field].hash;
3fbf9cbb
LP
736 r = journal_file_move_to_object(f, p, OBJECT_DATA, &o);
737 if (r < 0)
738 return r;
739
de7b95cd
LP
740 if (h != o->data.hash)
741 return -EBADMSG;
742
3fbf9cbb
LP
743 l = le64toh(o->object.size) - offsetof(Object, data.payload);
744 t = (size_t) l;
745
746 /* We can't read objects larger than 4G on a 32bit machine */
747 if ((uint64_t) t != l)
748 return -E2BIG;
749
750 *data = o->data.payload;
751 *size = t;
752
7210bfb3 753 j->current_field ++;
3fbf9cbb
LP
754
755 return 1;
756}
c2373f84
LP
757
758int sd_journal_seek_head(sd_journal *j) {
759 assert(j);
760 return -EINVAL;
761}
762
763int sd_journal_seek_tail(sd_journal *j) {
764 assert(j);
765 return -EINVAL;
766}