]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/sd-journal.c
logs-show: various cleanups
[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
5430f7f2
LP
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
87d2c1ff
LP
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
5430f7f2 16 Lesser General Public License for more details.
87d2c1ff 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
87d2c1ff
LP
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>
50f20cfd
LP
25#include <unistd.h>
26#include <sys/inotify.h>
e02d1cf7 27#include <sys/poll.h>
87d2c1ff
LP
28
29#include "sd-journal.h"
30#include "journal-def.h"
cec736d2 31#include "journal-file.h"
260a2be4 32#include "hashmap.h"
cec736d2 33#include "list.h"
9eb977db 34#include "path-util.h"
de7b95cd 35#include "lookup3.h"
807e17f0 36#include "compress.h"
cf244689 37#include "journal-internal.h"
87d2c1ff 38
cab8ac60
LP
39#define JOURNAL_FILES_MAX 1024
40
de190aef 41static void detach_location(sd_journal *j) {
8f9b6cd9
LP
42 Iterator i;
43 JournalFile *f;
44
45 assert(j);
46
47 j->current_file = NULL;
48 j->current_field = 0;
49
50 HASHMAP_FOREACH(f, j->files, i)
51 f->current_offset = 0;
52}
53
de190aef
LP
54static void reset_location(sd_journal *j) {
55 assert(j);
56
57 detach_location(j);
58 zero(j->current_location);
59}
60
61static void init_location(Location *l, JournalFile *f, Object *o) {
62 assert(l);
63 assert(f);
64 assert(o->object.type == OBJECT_ENTRY);
65
66 l->type = LOCATION_DISCRETE;
67 l->seqnum = le64toh(o->entry.seqnum);
68 l->seqnum_id = f->header->seqnum_id;
69 l->realtime = le64toh(o->entry.realtime);
70 l->monotonic = le64toh(o->entry.monotonic);
ce3fd7e7 71 l->boot_id = o->entry.boot_id;
de190aef
LP
72 l->xor_hash = le64toh(o->entry.xor_hash);
73
74 l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true;
75}
76
77static void set_location(sd_journal *j, JournalFile *f, Object *o, uint64_t offset) {
78 assert(j);
79 assert(f);
80 assert(o);
81
82 init_location(&j->current_location, f, o);
83
84 j->current_file = f;
85 j->current_field = 0;
86
87 f->current_offset = offset;
88}
89
cbdca852
LP
90static int match_is_valid(const void *data, size_t size) {
91 const char *b, *p;
92
93 assert(data);
94
95 if (size < 2)
96 return false;
97
98 if (startswith(data, "__"))
99 return false;
100
101 b = data;
102 for (p = b; p < b + size; p++) {
103
104 if (*p == '=')
105 return p > b;
106
107 if (*p == '_')
108 continue;
109
110 if (*p >= 'A' && *p <= 'Z')
111 continue;
112
113 if (*p >= '0' && *p <= '9')
114 continue;
115
116 return false;
117 }
118
119 return false;
120}
121
122static bool same_field(const void *_a, size_t s, const void *_b, size_t t) {
de190aef
LP
123 const uint8_t *a = _a, *b = _b;
124 size_t j;
de190aef
LP
125
126 for (j = 0; j < s && j < t; j++) {
127
de190aef 128 if (a[j] != b[j])
cbdca852 129 return false;
de190aef 130
cbdca852
LP
131 if (a[j] == '=')
132 return true;
de190aef
LP
133 }
134
cbdca852
LP
135 return true;
136}
137
138static Match *match_new(Match *p, MatchType t) {
139 Match *m;
140
141 m = new0(Match, 1);
142 if (!m)
143 return NULL;
144
145 m->type = t;
146
147 if (p) {
148 m->parent = p;
149 LIST_PREPEND(Match, matches, p->matches, m);
150 }
151
152 return m;
153}
154
155static void match_free(Match *m) {
156 assert(m);
157
158 while (m->matches)
159 match_free(m->matches);
160
161 if (m->parent)
162 LIST_REMOVE(Match, matches, m->parent->matches, m);
163
164 free(m->data);
165 free(m);
166}
167
168static void match_free_if_empty(Match *m) {
169 assert(m);
170
171 if (m->matches)
172 return;
173
174 match_free(m);
de190aef
LP
175}
176
a5344d2c 177_public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
cbdca852 178 Match *l2, *l3, *add_here = NULL, *m;
4fd052ae 179 le64_t le_hash;
87d2c1ff 180
a5344d2c
LP
181 if (!j)
182 return -EINVAL;
cbdca852 183
a5344d2c
LP
184 if (!data)
185 return -EINVAL;
cbdca852
LP
186
187 if (size == 0)
188 size = strlen(data);
189
190 if (!match_is_valid(data, size))
1cc101f1
LP
191 return -EINVAL;
192
cbdca852
LP
193 /* level 0: OR term
194 * level 1: AND terms
195 * level 2: OR terms
196 * level 3: concrete matches */
197
198 if (!j->level0) {
199 j->level0 = match_new(NULL, MATCH_OR_TERM);
200 if (!j->level0)
201 return -ENOMEM;
202 }
203
204 if (!j->level1) {
205 j->level1 = match_new(j->level0, MATCH_AND_TERM);
206 if (!j->level1)
207 return -ENOMEM;
208 }
209
210 assert(j->level0->type == MATCH_OR_TERM);
211 assert(j->level1->type == MATCH_AND_TERM);
ab4979d2 212
de190aef
LP
213 le_hash = htole64(hash64(data, size));
214
cbdca852
LP
215 LIST_FOREACH(matches, l2, j->level1->matches) {
216 assert(l2->type == MATCH_OR_TERM);
de190aef 217
cbdca852
LP
218 LIST_FOREACH(matches, l3, l2->matches) {
219 assert(l3->type == MATCH_DISCRETE);
de190aef 220
cbdca852
LP
221 /* Exactly the same match already? Then ignore
222 * this addition */
223 if (l3->le_hash == le_hash &&
224 l3->size == size &&
225 memcmp(l3->data, data, size) == 0)
226 return 0;
227
228 /* Same field? Then let's add this to this OR term */
229 if (same_field(data, size, l3->data, l3->size)) {
230 add_here = l2;
231 break;
232 }
233 }
234
235 if (add_here)
236 break;
de190aef
LP
237 }
238
cbdca852
LP
239 if (!add_here) {
240 add_here = match_new(j->level1, MATCH_OR_TERM);
241 if (!add_here)
242 goto fail;
243 }
244
245 m = match_new(add_here, MATCH_DISCRETE);
cec736d2 246 if (!m)
cbdca852 247 goto fail;
87d2c1ff 248
cbdca852 249 m->le_hash = le_hash;
1cc101f1 250 m->size = size;
cbdca852
LP
251 m->data = memdup(data, size);
252 if (!m->data)
253 goto fail;
254
255 detach_location(j);
256
257 return 0;
258
259fail:
260 if (add_here)
261 match_free_if_empty(add_here);
262
263 if (j->level1)
264 match_free_if_empty(j->level1);
265
266 if (j->level0)
267 match_free_if_empty(j->level0);
268
269 return -ENOMEM;
270}
271
272_public_ int sd_journal_add_disjunction(sd_journal *j) {
273 Match *m;
274
275 assert(j);
1cc101f1 276
cbdca852
LP
277 if (!j->level0)
278 return 0;
279
280 if (!j->level1)
281 return 0;
282
283 if (!j->level1->matches)
284 return 0;
285
286 m = match_new(j->level0, MATCH_AND_TERM);
287 if (!m)
cec736d2 288 return -ENOMEM;
cbdca852
LP
289
290 j->level1 = m;
291 return 0;
292}
293
294static char *match_make_string(Match *m) {
295 char *p, *r;
296 Match *i;
297 bool enclose = false;
298
299 if (!m)
300 return strdup("");
301
302 if (m->type == MATCH_DISCRETE)
303 return strndup(m->data, m->size);
304
305 p = NULL;
306 LIST_FOREACH(matches, i, m->matches) {
307 char *t, *k;
308
309 t = match_make_string(i);
310 if (!t) {
311 free(p);
312 return NULL;
313 }
314
315 if (p) {
b7def684 316 k = strjoin(p, m->type == MATCH_OR_TERM ? " OR " : " AND ", t, NULL);
cbdca852
LP
317 free(p);
318 free(t);
319
320 if (!k)
321 return NULL;
322
323 p = k;
324
325 enclose = true;
326 } else {
327 free(p);
328 p = t;
329 }
87d2c1ff
LP
330 }
331
cbdca852 332 if (enclose) {
b7def684 333 r = strjoin("(", p, ")", NULL);
cbdca852
LP
334 free(p);
335 return r;
336 }
87d2c1ff 337
cbdca852
LP
338 return p;
339}
de7b95cd 340
cbdca852
LP
341char *journal_make_match_string(sd_journal *j) {
342 assert(j);
8f9b6cd9 343
cbdca852 344 return match_make_string(j->level0);
87d2c1ff
LP
345}
346
a5344d2c 347_public_ void sd_journal_flush_matches(sd_journal *j) {
cbdca852 348
a5344d2c
LP
349 if (!j)
350 return;
87d2c1ff 351
cbdca852
LP
352 if (j->level0)
353 match_free(j->level0);
de7b95cd 354
cbdca852 355 j->level0 = j->level1 = NULL;
8f9b6cd9 356
de190aef 357 detach_location(j);
87d2c1ff
LP
358}
359
468b21de
LP
360static int compare_entry_order(JournalFile *af, Object *_ao,
361 JournalFile *bf, uint64_t bp) {
87d2c1ff 362
cec736d2 363 uint64_t a, b;
468b21de
LP
364 Object *ao, *bo;
365 int r;
87d2c1ff 366
de190aef 367 assert(af);
de190aef 368 assert(bf);
468b21de
LP
369 assert(_ao);
370
371 /* The mmap cache might invalidate the object from the first
372 * file if we look at the one from the second file. Hence
373 * temporarily copy the header of the first one, and look at
374 * that only. */
375 ao = alloca(offsetof(EntryObject, items));
376 memcpy(ao, _ao, offsetof(EntryObject, items));
377
378 r = journal_file_move_to_object(bf, OBJECT_ENTRY, bp, &bo);
379 if (r < 0)
380 return strcmp(af->path, bf->path);
de190aef 381
ae2cc8ef 382 /* We operate on two different files here, hence we can access
1cc101f1
LP
383 * two objects at the same time, which we normally can't.
384 *
385 * If contents and timestamps match, these entries are
386 * identical, even if the seqnum does not match */
387
388 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
389 ao->entry.monotonic == bo->entry.monotonic &&
390 ao->entry.realtime == bo->entry.realtime &&
391 ao->entry.xor_hash == bo->entry.xor_hash)
392 return 0;
ae2cc8ef 393
cec736d2 394 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
87d2c1ff 395
cec736d2
LP
396 /* If this is from the same seqnum source, compare
397 * seqnums */
398 a = le64toh(ao->entry.seqnum);
399 b = le64toh(bo->entry.seqnum);
87d2c1ff 400
ae2cc8ef
LP
401 if (a < b)
402 return -1;
403 if (a > b)
404 return 1;
1cc101f1
LP
405
406 /* Wow! This is weird, different data but the same
407 * seqnums? Something is borked, but let's make the
408 * best of it and compare by time. */
ae2cc8ef 409 }
87d2c1ff 410
ae2cc8ef 411 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
87d2c1ff 412
cec736d2
LP
413 /* If the boot id matches compare monotonic time */
414 a = le64toh(ao->entry.monotonic);
415 b = le64toh(bo->entry.monotonic);
87d2c1ff 416
ae2cc8ef
LP
417 if (a < b)
418 return -1;
419 if (a > b)
420 return 1;
87d2c1ff
LP
421 }
422
ae2cc8ef
LP
423 /* Otherwise compare UTC time */
424 a = le64toh(ao->entry.realtime);
c4aff78b 425 b = le64toh(bo->entry.realtime);
ae2cc8ef
LP
426
427 if (a < b)
428 return -1;
429 if (a > b)
430 return 1;
431
432 /* Finally, compare by contents */
433 a = le64toh(ao->entry.xor_hash);
c4aff78b 434 b = le64toh(bo->entry.xor_hash);
ae2cc8ef
LP
435
436 if (a < b)
437 return -1;
438 if (a > b)
439 return 1;
440
441 return 0;
87d2c1ff
LP
442}
443
de190aef
LP
444static int compare_with_location(JournalFile *af, Object *ao, Location *l) {
445 uint64_t a;
446
447 assert(af);
448 assert(ao);
449 assert(l);
450 assert(l->type == LOCATION_DISCRETE);
451
452 if (l->monotonic_set &&
453 sd_id128_equal(ao->entry.boot_id, l->boot_id) &&
454 l->realtime_set &&
455 le64toh(ao->entry.realtime) == l->realtime &&
456 l->xor_hash_set &&
457 le64toh(ao->entry.xor_hash) == l->xor_hash)
458 return 0;
459
460 if (l->seqnum_set &&
461 sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) {
462
463 a = le64toh(ao->entry.seqnum);
464
465 if (a < l->seqnum)
466 return -1;
467 if (a > l->seqnum)
468 return 1;
469 }
470
471 if (l->monotonic_set &&
472 sd_id128_equal(ao->entry.boot_id, l->boot_id)) {
473
474 a = le64toh(ao->entry.monotonic);
475
476 if (a < l->monotonic)
477 return -1;
478 if (a > l->monotonic)
479 return 1;
480 }
481
482 if (l->realtime_set) {
483
484 a = le64toh(ao->entry.realtime);
485
486 if (a < l->realtime)
487 return -1;
488 if (a > l->realtime)
489 return 1;
490 }
491
492 if (l->xor_hash_set) {
493 a = le64toh(ao->entry.xor_hash);
494
495 if (a < l->xor_hash)
496 return -1;
497 if (a > l->xor_hash)
498 return 1;
499 }
500
501 return 0;
502}
503
cbdca852
LP
504static int next_for_match(
505 sd_journal *j,
506 Match *m,
507 JournalFile *f,
508 uint64_t after_offset,
509 direction_t direction,
510 Object **ret,
511 uint64_t *offset) {
512
de7b95cd 513 int r;
cbdca852
LP
514 uint64_t np = 0;
515 Object *n;
de7b95cd
LP
516
517 assert(j);
cbdca852
LP
518 assert(m);
519 assert(f);
de7b95cd 520
cbdca852
LP
521 if (m->type == MATCH_DISCRETE) {
522 uint64_t dp;
de190aef 523
cbdca852 524 r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp);
de190aef
LP
525 if (r <= 0)
526 return r;
527
cbdca852 528 return journal_file_move_to_entry_by_offset_for_data(f, dp, after_offset, direction, ret, offset);
de190aef 529
cbdca852
LP
530 } else if (m->type == MATCH_OR_TERM) {
531 Match *i;
de7b95cd 532
cbdca852 533 /* Find the earliest match beyond after_offset */
de190aef 534
cbdca852
LP
535 LIST_FOREACH(matches, i, m->matches) {
536 uint64_t cp;
de190aef 537
cbdca852 538 r = next_for_match(j, i, f, after_offset, direction, NULL, &cp);
b4e5f920
LP
539 if (r < 0)
540 return r;
cbdca852
LP
541 else if (r > 0) {
542 if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp))
543 np = cp;
544 }
545 }
b4e5f920 546
cbdca852
LP
547 } else if (m->type == MATCH_AND_TERM) {
548 Match *i;
549 bool continue_looking;
de190aef 550
cbdca852
LP
551 /* Always jump to the next matching entry and repeat
552 * this until we fine and offset that matches for all
553 * matches. */
de190aef 554
cbdca852
LP
555 if (!m->matches)
556 return 0;
de7b95cd 557
cbdca852
LP
558 np = 0;
559 do {
560 continue_looking = false;
de190aef 561
cbdca852
LP
562 LIST_FOREACH(matches, i, m->matches) {
563 uint64_t cp, limit;
de190aef 564
cbdca852
LP
565 if (np == 0)
566 limit = after_offset;
567 else if (direction == DIRECTION_DOWN)
568 limit = MAX(np, after_offset);
569 else
570 limit = MIN(np, after_offset);
de190aef 571
cbdca852
LP
572 r = next_for_match(j, i, f, limit, direction, NULL, &cp);
573 if (r <= 0)
574 return r;
de190aef 575
cbdca852
LP
576 if ((direction == DIRECTION_DOWN ? cp >= after_offset : cp <= after_offset) &&
577 (np == 0 || (direction == DIRECTION_DOWN ? cp > np : np < cp))) {
578 np = cp;
579 continue_looking = true;
de190aef
LP
580 }
581 }
de190aef 582
cbdca852
LP
583 } while (continue_looking);
584 }
de190aef 585
cbdca852
LP
586 if (np == 0)
587 return 0;
de190aef 588
cbdca852
LP
589 r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n);
590 if (r < 0)
591 return r;
de7b95cd 592
de190aef 593 if (ret)
cbdca852 594 *ret = n;
de190aef 595 if (offset)
cbdca852 596 *offset = np;
de190aef
LP
597
598 return 1;
599}
600
cbdca852
LP
601static int find_location_for_match(
602 sd_journal *j,
603 Match *m,
604 JournalFile *f,
605 direction_t direction,
606 Object **ret,
607 uint64_t *offset) {
608
de190aef 609 int r;
de190aef
LP
610
611 assert(j);
cbdca852 612 assert(m);
de190aef 613 assert(f);
de190aef 614
cbdca852
LP
615 if (m->type == MATCH_DISCRETE) {
616 uint64_t dp;
de190aef 617
cbdca852 618 r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp);
de7b95cd
LP
619 if (r <= 0)
620 return r;
621
cbdca852 622 /* FIXME: missing: find by monotonic */
de7b95cd 623
cbdca852
LP
624 if (j->current_location.type == LOCATION_HEAD)
625 return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, ret, offset);
626 if (j->current_location.type == LOCATION_TAIL)
627 return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, ret, offset);
628 if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
629 return journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, ret, offset);
630 if (j->current_location.monotonic_set) {
631 r = journal_file_move_to_entry_by_monotonic_for_data(f, dp, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset);
632 if (r != -ENOENT)
633 return r;
634 }
635 if (j->current_location.realtime_set)
636 return journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, ret, offset);
de190aef 637
cbdca852 638 return journal_file_next_entry_for_data(f, NULL, 0, dp, direction, ret, offset);
de7b95cd 639
cbdca852
LP
640 } else if (m->type == MATCH_OR_TERM) {
641 uint64_t np = 0;
642 Object *n;
643 Match *i;
de7b95cd 644
cbdca852 645 /* Find the earliest match */
de7b95cd 646
cbdca852
LP
647 LIST_FOREACH(matches, i, m->matches) {
648 uint64_t cp;
649
650 r = find_location_for_match(j, i, f, direction, NULL, &cp);
651 if (r < 0)
652 return r;
653 else if (r > 0) {
654 if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp))
655 np = cp;
de190aef 656 }
cbdca852 657 }
de190aef 658
cbdca852
LP
659 if (np == 0)
660 return 0;
de7b95cd 661
cbdca852
LP
662 r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n);
663 if (r < 0)
664 return r;
de7b95cd 665
cbdca852
LP
666 if (ret)
667 *ret = n;
668 if (offset)
669 *offset = np;
de190aef 670
cbdca852 671 return 1;
e892bd17 672
cbdca852
LP
673 } else {
674 Match *i;
675 uint64_t np = 0;
676
677 assert(m->type == MATCH_AND_TERM);
678
679 /* First jump to the last match, and then find the
680 * next one where all matches match */
681
682 if (!m->matches)
683 return 0;
684
685 LIST_FOREACH(matches, i, m->matches) {
686 uint64_t cp;
687
688 r = find_location_for_match(j, i, f, direction, NULL, &cp);
689 if (r <= 0)
4b067dc9
LP
690 return r;
691
cbdca852
LP
692 if (np == 0 || (direction == DIRECTION_DOWN ? np < cp : np > cp))
693 np = cp;
de7b95cd
LP
694 }
695
cbdca852
LP
696 return next_for_match(j, m, f, np, direction, ret, offset);
697 }
698}
de190aef 699
cbdca852
LP
700static int find_location_with_matches(
701 sd_journal *j,
702 JournalFile *f,
703 direction_t direction,
704 Object **ret,
705 uint64_t *offset) {
706
707 int r;
708
709 assert(j);
710 assert(f);
711 assert(ret);
712 assert(offset);
713
714 if (!j->level0) {
715 /* No matches is simple */
716
717 if (j->current_location.type == LOCATION_HEAD)
718 return journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, ret, offset);
719 if (j->current_location.type == LOCATION_TAIL)
720 return journal_file_next_entry(f, NULL, 0, DIRECTION_UP, ret, offset);
721 if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
722 return journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, ret, offset);
723 if (j->current_location.monotonic_set) {
724 r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset);
725 if (r != -ENOENT)
726 return r;
de7b95cd 727 }
cbdca852
LP
728 if (j->current_location.realtime_set)
729 return journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, ret, offset);
de7b95cd 730
cbdca852
LP
731 return journal_file_next_entry(f, NULL, 0, direction, ret, offset);
732 } else
733 return find_location_for_match(j, j->level0, f, direction, ret, offset);
734}
de7b95cd 735
cbdca852
LP
736static int next_with_matches(
737 sd_journal *j,
738 JournalFile *f,
739 direction_t direction,
740 Object **ret,
741 uint64_t *offset) {
742
743 Object *c;
744 uint64_t cp;
745
746 assert(j);
747 assert(f);
748 assert(ret);
749 assert(offset);
750
751 c = *ret;
752 cp = *offset;
753
754 /* No matches is easy. We simple advance the file
755 * pointer by one. */
756 if (!j->level0)
757 return journal_file_next_entry(f, c, cp, direction, ret, offset);
758
759 /* If we have a match then we look for the next matching entry
49f43d5f 760 * with an offset at least one step larger */
cbdca852 761 return next_for_match(j, j->level0, f, direction == DIRECTION_DOWN ? cp+1 : cp-1, direction, ret, offset);
de7b95cd
LP
762}
763
de190aef
LP
764static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
765 Object *c;
766 uint64_t cp;
cbdca852 767 int r;
de190aef
LP
768
769 assert(j);
770 assert(f);
771
772 if (f->current_offset > 0) {
466ccd92
LP
773 cp = f->current_offset;
774
775 r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
de190aef
LP
776 if (r < 0)
777 return r;
778
de190aef
LP
779 r = next_with_matches(j, f, direction, &c, &cp);
780 if (r <= 0)
781 return r;
de190aef 782 } else {
cbdca852 783 r = find_location_with_matches(j, f, direction, &c, &cp);
de190aef
LP
784 if (r <= 0)
785 return r;
de190aef
LP
786 }
787
cbdca852
LP
788 /* OK, we found the spot, now let's advance until to an entry
789 * that is actually different from what we were previously
790 * looking at. This is necessary to handle entries which exist
791 * in two (or more) journal files, and which shall all be
792 * suppressed but one. */
793
de190aef
LP
794 for (;;) {
795 bool found;
796
797 if (j->current_location.type == LOCATION_DISCRETE) {
798 int k;
799
800 k = compare_with_location(f, c, &j->current_location);
801 if (direction == DIRECTION_DOWN)
cbdca852 802 found = k > 0;
de190aef 803 else
cbdca852 804 found = k < 0;
de190aef
LP
805 } else
806 found = true;
807
808 if (found) {
809 if (ret)
810 *ret = c;
811 if (offset)
812 *offset = cp;
813 return 1;
814 }
815
816 r = next_with_matches(j, f, direction, &c, &cp);
817 if (r <= 0)
818 return r;
819 }
820}
821
e892bd17 822static int real_journal_next(sd_journal *j, direction_t direction) {
468b21de
LP
823 JournalFile *f, *new_file = NULL;
824 uint64_t new_offset = 0;
825 Object *o;
826 uint64_t p;
cec736d2 827 Iterator i;
87d2c1ff
LP
828 int r;
829
a5344d2c
LP
830 if (!j)
831 return -EINVAL;
87d2c1ff 832
cec736d2 833 HASHMAP_FOREACH(f, j->files, i) {
de190aef 834 bool found;
87d2c1ff 835
de190aef 836 r = next_beyond_location(j, f, direction, &o, &p);
e590af26
LP
837 if (r < 0) {
838 log_debug("Can't iterate through %s, ignoring: %s", f->path, strerror(-r));
839 continue;
840 } else if (r == 0)
cec736d2 841 continue;
87d2c1ff 842
468b21de 843 if (!new_file)
de190aef
LP
844 found = true;
845 else {
846 int k;
847
468b21de 848 k = compare_entry_order(f, o, new_file, new_offset);
de190aef
LP
849
850 if (direction == DIRECTION_DOWN)
851 found = k < 0;
852 else
853 found = k > 0;
854 }
855
856 if (found) {
468b21de 857 new_file = f;
cec736d2 858 new_offset = p;
87d2c1ff 859 }
87d2c1ff
LP
860 }
861
468b21de 862 if (!new_file)
de190aef 863 return 0;
ae2cc8ef 864
468b21de
LP
865 r = journal_file_move_to_object(new_file, OBJECT_ENTRY, new_offset, &o);
866 if (r < 0)
867 return r;
868
869 set_location(j, new_file, o, new_offset);
ae2cc8ef 870
de190aef
LP
871 return 1;
872}
ae2cc8ef 873
a5344d2c 874_public_ int sd_journal_next(sd_journal *j) {
de190aef
LP
875 return real_journal_next(j, DIRECTION_DOWN);
876}
ae2cc8ef 877
a5344d2c 878_public_ int sd_journal_previous(sd_journal *j) {
de190aef
LP
879 return real_journal_next(j, DIRECTION_UP);
880}
ae2cc8ef 881
6f003b43 882static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
de190aef 883 int c = 0, r;
ae2cc8ef 884
a5344d2c
LP
885 if (!j)
886 return -EINVAL;
de190aef 887
6f003b43
LP
888 if (skip == 0) {
889 /* If this is not a discrete skip, then at least
890 * resolve the current location */
891 if (j->current_location.type != LOCATION_DISCRETE)
892 return real_journal_next(j, direction);
893
894 return 0;
895 }
896
897 do {
898 r = real_journal_next(j, direction);
de190aef
LP
899 if (r < 0)
900 return r;
901
902 if (r == 0)
903 return c;
904
905 skip--;
906 c++;
6f003b43 907 } while (skip > 0);
87d2c1ff 908
de190aef 909 return c;
87d2c1ff
LP
910}
911
6f003b43
LP
912_public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
913 return real_journal_next_skip(j, DIRECTION_DOWN, skip);
914}
de190aef 915
6f003b43
LP
916_public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
917 return real_journal_next_skip(j, DIRECTION_UP, skip);
87d2c1ff
LP
918}
919
a5344d2c 920_public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
cec736d2 921 Object *o;
87d2c1ff 922 int r;
3fbf9cbb 923 char bid[33], sid[33];
87d2c1ff 924
a5344d2c
LP
925 if (!j)
926 return -EINVAL;
927 if (!cursor)
928 return -EINVAL;
87d2c1ff 929
3fbf9cbb
LP
930 if (!j->current_file || j->current_file->current_offset <= 0)
931 return -EADDRNOTAVAIL;
87d2c1ff 932
de190aef 933 r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
87d2c1ff
LP
934 if (r < 0)
935 return r;
936
3fbf9cbb
LP
937 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
938 sd_id128_to_string(o->entry.boot_id, bid);
87d2c1ff 939
3fbf9cbb
LP
940 if (asprintf(cursor,
941 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
942 sid, (unsigned long long) le64toh(o->entry.seqnum),
943 bid, (unsigned long long) le64toh(o->entry.monotonic),
944 (unsigned long long) le64toh(o->entry.realtime),
945 (unsigned long long) le64toh(o->entry.xor_hash),
9eb977db 946 path_get_file_name(j->current_file->path)) < 0)
3fbf9cbb 947 return -ENOMEM;
87d2c1ff
LP
948
949 return 1;
950}
951
a5344d2c 952_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
de190aef
LP
953 char *w;
954 size_t l;
955 char *state;
956 unsigned long long seqnum, monotonic, realtime, xor_hash;
957 bool
958 seqnum_id_set = false,
959 seqnum_set = false,
960 boot_id_set = false,
961 monotonic_set = false,
962 realtime_set = false,
963 xor_hash_set = false;
964 sd_id128_t seqnum_id, boot_id;
965
a5344d2c
LP
966 if (!j)
967 return -EINVAL;
968 if (!cursor)
969 return -EINVAL;
de190aef
LP
970
971 FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
972 char *item;
973 int k = 0;
974
975 if (l < 2 || w[1] != '=')
976 return -EINVAL;
977
978 item = strndup(w, l);
979 if (!item)
980 return -ENOMEM;
981
982 switch (w[0]) {
983
984 case 's':
985 seqnum_id_set = true;
986 k = sd_id128_from_string(w+2, &seqnum_id);
987 break;
988
989 case 'i':
990 seqnum_set = true;
991 if (sscanf(w+2, "%llx", &seqnum) != 1)
992 k = -EINVAL;
993 break;
994
995 case 'b':
996 boot_id_set = true;
997 k = sd_id128_from_string(w+2, &boot_id);
998 break;
999
1000 case 'm':
1001 monotonic_set = true;
1002 if (sscanf(w+2, "%llx", &monotonic) != 1)
1003 k = -EINVAL;
1004 break;
1005
1006 case 't':
1007 realtime_set = true;
1008 if (sscanf(w+2, "%llx", &realtime) != 1)
1009 k = -EINVAL;
1010 break;
1011
1012 case 'x':
1013 xor_hash_set = true;
1014 if (sscanf(w+2, "%llx", &xor_hash) != 1)
1015 k = -EINVAL;
1016 break;
1017 }
1018
1019 free(item);
1020
1021 if (k < 0)
1022 return k;
1023 }
1024
1025 if ((!seqnum_set || !seqnum_id_set) &&
1026 (!monotonic_set || !boot_id_set) &&
1027 !realtime_set)
1028 return -EINVAL;
1029
1030 reset_location(j);
1031
1032 j->current_location.type = LOCATION_DISCRETE;
1033
1034 if (realtime_set) {
1035 j->current_location.realtime = (uint64_t) realtime;
1036 j->current_location.realtime_set = true;
1037 }
1038
1039 if (seqnum_set && seqnum_id_set) {
1040 j->current_location.seqnum = (uint64_t) seqnum;
1041 j->current_location.seqnum_id = seqnum_id;
1042 j->current_location.seqnum_set = true;
1043 }
1044
1045 if (monotonic_set && boot_id_set) {
1046 j->current_location.monotonic = (uint64_t) monotonic;
1047 j->current_location.boot_id = boot_id;
1048 j->current_location.monotonic_set = true;
1049 }
1050
1051 if (xor_hash_set) {
1052 j->current_location.xor_hash = (uint64_t) xor_hash;
1053 j->current_location.xor_hash_set = true;
1054 }
1055
1056 return 0;
1057}
1058
a5344d2c
LP
1059_public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
1060 if (!j)
1061 return -EINVAL;
de190aef
LP
1062
1063 reset_location(j);
1064 j->current_location.type = LOCATION_DISCRETE;
1065 j->current_location.boot_id = boot_id;
1066 j->current_location.monotonic = usec;
1067 j->current_location.monotonic_set = true;
1068
1069 return 0;
1070}
1071
a5344d2c
LP
1072_public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
1073 if (!j)
1074 return -EINVAL;
de190aef
LP
1075
1076 reset_location(j);
1077 j->current_location.type = LOCATION_DISCRETE;
1078 j->current_location.realtime = usec;
1079 j->current_location.realtime_set = true;
1080
1081 return 0;
1082}
1083
a5344d2c
LP
1084_public_ int sd_journal_seek_head(sd_journal *j) {
1085 if (!j)
1086 return -EINVAL;
de190aef
LP
1087
1088 reset_location(j);
1089 j->current_location.type = LOCATION_HEAD;
1090
1091 return 0;
1092}
1093
a5344d2c
LP
1094_public_ int sd_journal_seek_tail(sd_journal *j) {
1095 if (!j)
1096 return -EINVAL;
de190aef
LP
1097
1098 reset_location(j);
1099 j->current_location.type = LOCATION_TAIL;
1100
1101 return 0;
87d2c1ff
LP
1102}
1103
a963990f
LP
1104static int add_file(sd_journal *j, const char *prefix, const char *filename) {
1105 char *path;
3fbf9cbb
LP
1106 int r;
1107 JournalFile *f;
1108
1109 assert(j);
1110 assert(prefix);
1111 assert(filename);
1112
cf244689 1113 if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) &&
38a6db16 1114 !(streq(filename, "system.journal") ||
de2c3907
LP
1115 streq(filename, "system.journal~") ||
1116 (startswith(filename, "system@") &&
1117 (endswith(filename, ".journal") || endswith(filename, ".journal~")))))
cf244689
LP
1118 return 0;
1119
b7def684 1120 path = strjoin(prefix, "/", filename, NULL);
a963990f 1121 if (!path)
3fbf9cbb
LP
1122 return -ENOMEM;
1123
a963990f
LP
1124 if (hashmap_get(j->files, path)) {
1125 free(path);
50f20cfd
LP
1126 return 0;
1127 }
1128
1129 if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
a963990f
LP
1130 log_debug("Too many open journal files, not adding %s, ignoring.", path);
1131 free(path);
50f20cfd
LP
1132 return 0;
1133 }
1134
16e9f408 1135 r = journal_file_open(path, O_RDONLY, 0, false, false, NULL, j->mmap, NULL, &f);
a963990f 1136 free(path);
3fbf9cbb
LP
1137
1138 if (r < 0) {
1139 if (errno == ENOENT)
1140 return 0;
1141
1142 return r;
1143 }
1144
72f59706 1145 /* journal_file_dump(f); */
de190aef 1146
3fbf9cbb
LP
1147 r = hashmap_put(j->files, f->path, f);
1148 if (r < 0) {
1149 journal_file_close(f);
1150 return r;
1151 }
1152
a963990f
LP
1153 j->current_invalidate_counter ++;
1154
50f20cfd
LP
1155 log_debug("File %s got added.", f->path);
1156
1157 return 0;
1158}
1159
a963990f
LP
1160static int remove_file(sd_journal *j, const char *prefix, const char *filename) {
1161 char *path;
50f20cfd
LP
1162 JournalFile *f;
1163
1164 assert(j);
1165 assert(prefix);
1166 assert(filename);
1167
b7def684 1168 path = strjoin(prefix, "/", filename, NULL);
a963990f 1169 if (!path)
50f20cfd
LP
1170 return -ENOMEM;
1171
a963990f
LP
1172 f = hashmap_get(j->files, path);
1173 free(path);
50f20cfd
LP
1174 if (!f)
1175 return 0;
1176
1177 hashmap_remove(j->files, f->path);
1178 journal_file_close(f);
1179
a963990f
LP
1180 j->current_invalidate_counter ++;
1181
50f20cfd 1182 log_debug("File %s got removed.", f->path);
3fbf9cbb
LP
1183 return 0;
1184}
1185
a963990f
LP
1186static int add_directory(sd_journal *j, const char *prefix, const char *dirname) {
1187 char *path;
3fbf9cbb
LP
1188 int r;
1189 DIR *d;
cf244689 1190 sd_id128_t id, mid;
a963990f 1191 Directory *m;
3fbf9cbb
LP
1192
1193 assert(j);
1194 assert(prefix);
a963990f 1195 assert(dirname);
3fbf9cbb 1196
cf244689 1197 if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
a963990f 1198 (sd_id128_from_string(dirname, &id) < 0 ||
cf244689
LP
1199 sd_id128_get_machine(&mid) < 0 ||
1200 !sd_id128_equal(id, mid)))
1201 return 0;
1202
b7def684 1203 path = strjoin(prefix, "/", dirname, NULL);
a963990f 1204 if (!path)
3fbf9cbb
LP
1205 return -ENOMEM;
1206
a963990f 1207 d = opendir(path);
3fbf9cbb 1208 if (!d) {
a963990f
LP
1209 log_debug("Failed to open %s: %m", path);
1210 free(path);
1211
3fbf9cbb
LP
1212 if (errno == ENOENT)
1213 return 0;
3fbf9cbb
LP
1214 return -errno;
1215 }
1216
a963990f
LP
1217 m = hashmap_get(j->directories_by_path, path);
1218 if (!m) {
1219 m = new0(Directory, 1);
1220 if (!m) {
1221 closedir(d);
1222 free(path);
1223 return -ENOMEM;
1224 }
1225
1226 m->is_root = false;
1227 m->path = path;
1228
1229 if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
1230 closedir(d);
1231 free(m->path);
1232 free(m);
1233 return -ENOMEM;
1234 }
1235
1236 j->current_invalidate_counter ++;
1237
1238 log_debug("Directory %s got added.", m->path);
1239
1240 } else if (m->is_root) {
1241 free (path);
1242 closedir(d);
1243 return 0;
1244 } else
1245 free(path);
1246
1247 if (m->wd <= 0 && j->inotify_fd >= 0) {
1248
1249 m->wd = inotify_add_watch(j->inotify_fd, m->path,
1250 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1251 IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|
4a842cad 1252 IN_ONLYDIR);
a963990f
LP
1253
1254 if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
1255 inotify_rm_watch(j->inotify_fd, m->wd);
1256 }
1257
1258 for (;;) {
7d5e9c0f
LP
1259 struct dirent *de;
1260 union dirent_storage buf;
a963990f 1261
7d5e9c0f 1262 r = readdir_r(d, &buf.de, &de);
a963990f
LP
1263 if (r != 0 || !de)
1264 break;
1265
de2c3907
LP
1266 if (dirent_is_file_with_suffix(de, ".journal") ||
1267 dirent_is_file_with_suffix(de, ".journal~")) {
a963990f
LP
1268 r = add_file(j, m->path, de->d_name);
1269 if (r < 0)
1270 log_debug("Failed to add file %s/%s: %s", m->path, de->d_name, strerror(-r));
1271 }
1272 }
1273
1274 closedir(d);
1275
1276 return 0;
1277}
1278
1279static int add_root_directory(sd_journal *j, const char *p) {
1280 DIR *d;
1281 Directory *m;
1282 int r;
1283
1284 assert(j);
1285 assert(p);
1286
1287 if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) &&
1288 !path_startswith(p, "/run"))
1289 return -EINVAL;
1290
1291 d = opendir(p);
1292 if (!d)
1293 return -errno;
1294
1295 m = hashmap_get(j->directories_by_path, p);
1296 if (!m) {
1297 m = new0(Directory, 1);
1298 if (!m) {
1299 closedir(d);
1300 return -ENOMEM;
1301 }
1302
1303 m->is_root = true;
1304 m->path = strdup(p);
1305 if (!m->path) {
1306 closedir(d);
1307 free(m);
1308 return -ENOMEM;
1309 }
1310
1311 if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
1312 closedir(d);
1313 free(m->path);
1314 free(m);
1315 return -ENOMEM;
1316 }
1317
1318 j->current_invalidate_counter ++;
1319
1320 log_debug("Root directory %s got added.", m->path);
1321
1322 } else if (!m->is_root) {
1323 closedir(d);
1324 return 0;
50f20cfd
LP
1325 }
1326
a963990f
LP
1327 if (m->wd <= 0 && j->inotify_fd >= 0) {
1328
1329 m->wd = inotify_add_watch(j->inotify_fd, m->path,
1330 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
4a842cad 1331 IN_ONLYDIR);
a963990f
LP
1332
1333 if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
1334 inotify_rm_watch(j->inotify_fd, m->wd);
1335 }
50f20cfd 1336
3fbf9cbb 1337 for (;;) {
7d5e9c0f
LP
1338 struct dirent *de;
1339 union dirent_storage buf;
a963990f 1340 sd_id128_t id;
3fbf9cbb 1341
7d5e9c0f 1342 r = readdir_r(d, &buf.de, &de);
3fbf9cbb
LP
1343 if (r != 0 || !de)
1344 break;
1345
de2c3907
LP
1346 if (dirent_is_file_with_suffix(de, ".journal") ||
1347 dirent_is_file_with_suffix(de, ".journal~")) {
a963990f
LP
1348 r = add_file(j, m->path, de->d_name);
1349 if (r < 0)
1350 log_debug("Failed to add file %s/%s: %s", m->path, de->d_name, strerror(-r));
3fbf9cbb 1351
6f5878a2 1352 } else if ((de->d_type == DT_DIR || de->d_type == DT_LNK || de->d_type == DT_UNKNOWN) &&
a963990f
LP
1353 sd_id128_from_string(de->d_name, &id) >= 0) {
1354
1355 r = add_directory(j, m->path, de->d_name);
1356 if (r < 0)
1357 log_debug("Failed to add directory %s/%s: %s", m->path, de->d_name, strerror(-r));
1358 }
3fbf9cbb
LP
1359 }
1360
1361 closedir(d);
1362
a963990f
LP
1363 return 0;
1364}
1365
1366static int remove_directory(sd_journal *j, Directory *d) {
1367 assert(j);
1368
1369 if (d->wd > 0) {
1370 hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd));
1371
1372 if (j->inotify_fd >= 0)
1373 inotify_rm_watch(j->inotify_fd, d->wd);
1374 }
1375
1376 hashmap_remove(j->directories_by_path, d->path);
1377
1378 if (d->is_root)
1379 log_debug("Root directory %s got removed.", d->path);
1380 else
1381 log_debug("Directory %s got removed.", d->path);
1382
1383 free(d->path);
1384 free(d);
50f20cfd 1385
3fbf9cbb
LP
1386 return 0;
1387}
1388
a963990f
LP
1389static int add_search_paths(sd_journal *j) {
1390
1391 const char search_paths[] =
1392 "/run/log/journal\0"
1393 "/var/log/journal\0";
1394 const char *p;
50f20cfd
LP
1395
1396 assert(j);
50f20cfd 1397
a963990f
LP
1398 /* We ignore most errors here, since the idea is to only open
1399 * what's actually accessible, and ignore the rest. */
50f20cfd 1400
a963990f
LP
1401 NULSTR_FOREACH(p, search_paths)
1402 add_root_directory(j, p);
50f20cfd 1403
a963990f 1404 return 0;
50f20cfd
LP
1405}
1406
a963990f 1407static int allocate_inotify(sd_journal *j) {
50f20cfd 1408 assert(j);
50f20cfd 1409
a963990f
LP
1410 if (j->inotify_fd < 0) {
1411 j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
1412 if (j->inotify_fd < 0)
1413 return -errno;
1414 }
50f20cfd 1415
a963990f
LP
1416 if (!j->directories_by_wd) {
1417 j->directories_by_wd = hashmap_new(trivial_hash_func, trivial_compare_func);
1418 if (!j->directories_by_wd)
1419 return -ENOMEM;
50f20cfd 1420 }
a963990f
LP
1421
1422 return 0;
50f20cfd
LP
1423}
1424
7827b1a1 1425static sd_journal *journal_new(int flags, const char *path) {
a963990f 1426 sd_journal *j;
50f20cfd 1427
a963990f
LP
1428 j = new0(sd_journal, 1);
1429 if (!j)
1430 return NULL;
50f20cfd 1431
a963990f
LP
1432 j->inotify_fd = -1;
1433 j->flags = flags;
50f20cfd 1434
7827b1a1
LP
1435 if (path) {
1436 j->path = strdup(path);
1437 if (!j->path) {
1438 free(j);
1439 return NULL;
1440 }
1441 }
1442
a963990f
LP
1443 j->files = hashmap_new(string_hash_func, string_compare_func);
1444 if (!j->files) {
7827b1a1 1445 free(j->path);
a963990f
LP
1446 free(j);
1447 return NULL;
1448 }
50f20cfd 1449
a963990f
LP
1450 j->directories_by_path = hashmap_new(string_hash_func, string_compare_func);
1451 if (!j->directories_by_path) {
1452 hashmap_free(j->files);
7827b1a1 1453 free(j->path);
a963990f
LP
1454 free(j);
1455 return NULL;
50f20cfd 1456 }
a963990f 1457
84168d80 1458 j->mmap = mmap_cache_new();
16e9f408
LP
1459 if (!j->mmap) {
1460 hashmap_free(j->files);
1461 hashmap_free(j->directories_by_path);
1462 free(j->path);
1463 free(j);
f2848607 1464 return NULL;
16e9f408
LP
1465 }
1466
a963990f 1467 return j;
50f20cfd
LP
1468}
1469
a5344d2c 1470_public_ int sd_journal_open(sd_journal **ret, int flags) {
87d2c1ff 1471 sd_journal *j;
3fbf9cbb 1472 int r;
87d2c1ff 1473
a5344d2c
LP
1474 if (!ret)
1475 return -EINVAL;
1476
1477 if (flags & ~(SD_JOURNAL_LOCAL_ONLY|
1478 SD_JOURNAL_RUNTIME_ONLY|
1479 SD_JOURNAL_SYSTEM_ONLY))
1480 return -EINVAL;
87d2c1ff 1481
7827b1a1 1482 j = journal_new(flags, NULL);
87d2c1ff
LP
1483 if (!j)
1484 return -ENOMEM;
1485
a963990f
LP
1486 r = add_search_paths(j);
1487 if (r < 0)
50f20cfd 1488 goto fail;
50f20cfd 1489
a963990f
LP
1490 *ret = j;
1491 return 0;
cf244689 1492
a963990f
LP
1493fail:
1494 sd_journal_close(j);
87d2c1ff 1495
a963990f
LP
1496 return r;
1497}
50f20cfd 1498
a963990f
LP
1499_public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) {
1500 sd_journal *j;
1501 int r;
87d2c1ff 1502
a963990f
LP
1503 if (!ret)
1504 return -EINVAL;
87d2c1ff 1505
a963990f
LP
1506 if (!path || !path_is_absolute(path))
1507 return -EINVAL;
87d2c1ff 1508
a963990f
LP
1509 if (flags != 0)
1510 return -EINVAL;
87d2c1ff 1511
7827b1a1 1512 j = journal_new(flags, path);
a963990f
LP
1513 if (!j)
1514 return -ENOMEM;
3fbf9cbb 1515
a963990f
LP
1516 r = add_root_directory(j, path);
1517 if (r < 0)
1518 goto fail;
87d2c1ff
LP
1519
1520 *ret = j;
1521 return 0;
1522
1523fail:
1524 sd_journal_close(j);
1525
1526 return r;
a963990f 1527}
87d2c1ff 1528
a5344d2c 1529_public_ void sd_journal_close(sd_journal *j) {
a963990f
LP
1530 Directory *d;
1531 JournalFile *f;
1532
a5344d2c
LP
1533 if (!j)
1534 return;
87d2c1ff 1535
a963990f
LP
1536 while ((f = hashmap_steal_first(j->files)))
1537 journal_file_close(f);
50f20cfd 1538
a963990f 1539 hashmap_free(j->files);
260a2be4 1540
a963990f
LP
1541 while ((d = hashmap_first(j->directories_by_path)))
1542 remove_directory(j, d);
260a2be4 1543
a963990f
LP
1544 while ((d = hashmap_first(j->directories_by_wd)))
1545 remove_directory(j, d);
87d2c1ff 1546
a963990f
LP
1547 hashmap_free(j->directories_by_path);
1548 hashmap_free(j->directories_by_wd);
1cc101f1 1549
50f20cfd
LP
1550 if (j->inotify_fd >= 0)
1551 close_nointr_nofail(j->inotify_fd);
1552
a963990f
LP
1553 sd_journal_flush_matches(j);
1554
16e9f408
LP
1555 if (j->mmap)
1556 mmap_cache_unref(j->mmap);
1557
7827b1a1 1558 free(j->path);
87d2c1ff
LP
1559 free(j);
1560}
3fbf9cbb 1561
a5344d2c 1562_public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
3fbf9cbb
LP
1563 Object *o;
1564 JournalFile *f;
1565 int r;
1566
a5344d2c
LP
1567 if (!j)
1568 return -EINVAL;
1569 if (!ret)
1570 return -EINVAL;
3fbf9cbb
LP
1571
1572 f = j->current_file;
1573 if (!f)
de190aef 1574 return -EADDRNOTAVAIL;
3fbf9cbb
LP
1575
1576 if (f->current_offset <= 0)
de190aef 1577 return -EADDRNOTAVAIL;
3fbf9cbb 1578
de190aef 1579 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
3fbf9cbb
LP
1580 if (r < 0)
1581 return r;
1582
1583 *ret = le64toh(o->entry.realtime);
de190aef 1584 return 0;
3fbf9cbb
LP
1585}
1586
a5344d2c 1587_public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
3fbf9cbb
LP
1588 Object *o;
1589 JournalFile *f;
1590 int r;
1591 sd_id128_t id;
1592
a5344d2c
LP
1593 if (!j)
1594 return -EINVAL;
3fbf9cbb
LP
1595
1596 f = j->current_file;
1597 if (!f)
de190aef 1598 return -EADDRNOTAVAIL;
3fbf9cbb
LP
1599
1600 if (f->current_offset <= 0)
de190aef 1601 return -EADDRNOTAVAIL;
3fbf9cbb 1602
de190aef 1603 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
3fbf9cbb
LP
1604 if (r < 0)
1605 return r;
1606
de190aef
LP
1607 if (ret_boot_id)
1608 *ret_boot_id = o->entry.boot_id;
1609 else {
1610 r = sd_id128_get_boot(&id);
1611 if (r < 0)
1612 return r;
3fbf9cbb 1613
de190aef 1614 if (!sd_id128_equal(id, o->entry.boot_id))
df50185b 1615 return -ESTALE;
de190aef 1616 }
3fbf9cbb 1617
14a65d65
LP
1618 if (ret)
1619 *ret = le64toh(o->entry.monotonic);
1620
de190aef 1621 return 0;
3fbf9cbb
LP
1622}
1623
362a3f81
LP
1624static bool field_is_valid(const char *field) {
1625 const char *p;
1626
1627 assert(field);
1628
1629 if (isempty(field))
1630 return false;
1631
1632 if (startswith(field, "__"))
1633 return false;
1634
1635 for (p = field; *p; p++) {
1636
1637 if (*p == '_')
1638 continue;
1639
1640 if (*p >= 'A' && *p <= 'Z')
1641 continue;
1642
1643 if (*p >= '0' && *p <= '9')
1644 continue;
1645
1646 return false;
1647 }
1648
1649 return true;
1650}
1651
a5344d2c 1652_public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
3fbf9cbb
LP
1653 JournalFile *f;
1654 uint64_t i, n;
1655 size_t field_length;
1656 int r;
1657 Object *o;
1658
a5344d2c
LP
1659 if (!j)
1660 return -EINVAL;
1661 if (!field)
1662 return -EINVAL;
1663 if (!data)
1664 return -EINVAL;
1665 if (!size)
1666 return -EINVAL;
3fbf9cbb 1667
362a3f81 1668 if (!field_is_valid(field))
3fbf9cbb
LP
1669 return -EINVAL;
1670
1671 f = j->current_file;
1672 if (!f)
de190aef 1673 return -EADDRNOTAVAIL;
3fbf9cbb
LP
1674
1675 if (f->current_offset <= 0)
de190aef 1676 return -EADDRNOTAVAIL;
3fbf9cbb 1677
de190aef 1678 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
3fbf9cbb
LP
1679 if (r < 0)
1680 return r;
1681
1682 field_length = strlen(field);
1683
1684 n = journal_file_entry_n_items(o);
1685 for (i = 0; i < n; i++) {
4fd052ae
FC
1686 uint64_t p, l;
1687 le64_t le_hash;
3fbf9cbb
LP
1688 size_t t;
1689
1690 p = le64toh(o->entry.items[i].object_offset);
807e17f0 1691 le_hash = o->entry.items[i].hash;
de190aef 1692 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
3fbf9cbb
LP
1693 if (r < 0)
1694 return r;
1695
de190aef 1696 if (le_hash != o->data.hash)
de7b95cd
LP
1697 return -EBADMSG;
1698
3fbf9cbb
LP
1699 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1700
807e17f0
LP
1701 if (o->object.flags & OBJECT_COMPRESSED) {
1702
1703#ifdef HAVE_XZ
1704 if (uncompress_startswith(o->data.payload, l,
1705 &f->compress_buffer, &f->compress_buffer_size,
1706 field, field_length, '=')) {
1707
1708 uint64_t rsize;
1709
1710 if (!uncompress_blob(o->data.payload, l,
1711 &f->compress_buffer, &f->compress_buffer_size, &rsize))
1712 return -EBADMSG;
1713
1714 *data = f->compress_buffer;
1715 *size = (size_t) rsize;
1716
1717 return 0;
1718 }
1719#else
1720 return -EPROTONOSUPPORT;
1721#endif
1722
1723 } else if (l >= field_length+1 &&
1724 memcmp(o->data.payload, field, field_length) == 0 &&
1725 o->data.payload[field_length] == '=') {
3fbf9cbb 1726
161e54f8 1727 t = (size_t) l;
3fbf9cbb 1728
161e54f8
LP
1729 if ((uint64_t) t != l)
1730 return -E2BIG;
3fbf9cbb 1731
161e54f8
LP
1732 *data = o->data.payload;
1733 *size = t;
3fbf9cbb 1734
de190aef 1735 return 0;
161e54f8 1736 }
3fbf9cbb 1737
de190aef 1738 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
161e54f8
LP
1739 if (r < 0)
1740 return r;
3fbf9cbb
LP
1741 }
1742
de190aef 1743 return -ENOENT;
3fbf9cbb
LP
1744}
1745
a5344d2c 1746_public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
3fbf9cbb 1747 JournalFile *f;
4fd052ae
FC
1748 uint64_t p, l, n;
1749 le64_t le_hash;
3fbf9cbb
LP
1750 int r;
1751 Object *o;
de190aef 1752 size_t t;
3fbf9cbb 1753
a5344d2c
LP
1754 if (!j)
1755 return -EINVAL;
1756 if (!data)
1757 return -EINVAL;
1758 if (!size)
1759 return -EINVAL;
3fbf9cbb
LP
1760
1761 f = j->current_file;
1762 if (!f)
de190aef 1763 return -EADDRNOTAVAIL;
3fbf9cbb
LP
1764
1765 if (f->current_offset <= 0)
de190aef 1766 return -EADDRNOTAVAIL;
3fbf9cbb 1767
de190aef 1768 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
3fbf9cbb
LP
1769 if (r < 0)
1770 return r;
1771
1772 n = journal_file_entry_n_items(o);
7210bfb3 1773 if (j->current_field >= n)
3fbf9cbb
LP
1774 return 0;
1775
7210bfb3 1776 p = le64toh(o->entry.items[j->current_field].object_offset);
de190aef
LP
1777 le_hash = o->entry.items[j->current_field].hash;
1778 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
3fbf9cbb
LP
1779 if (r < 0)
1780 return r;
1781
de190aef 1782 if (le_hash != o->data.hash)
de7b95cd
LP
1783 return -EBADMSG;
1784
3fbf9cbb
LP
1785 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1786 t = (size_t) l;
1787
1788 /* We can't read objects larger than 4G on a 32bit machine */
1789 if ((uint64_t) t != l)
1790 return -E2BIG;
1791
807e17f0
LP
1792 if (o->object.flags & OBJECT_COMPRESSED) {
1793#ifdef HAVE_XZ
1794 uint64_t rsize;
1795
1796 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
1797 return -EBADMSG;
1798
1799 *data = f->compress_buffer;
1800 *size = (size_t) rsize;
1801#else
1802 return -EPROTONOSUPPORT;
1803#endif
1804 } else {
1805 *data = o->data.payload;
1806 *size = t;
1807 }
3fbf9cbb 1808
7210bfb3 1809 j->current_field ++;
3fbf9cbb
LP
1810
1811 return 1;
1812}
c2373f84 1813
a5344d2c
LP
1814_public_ void sd_journal_restart_data(sd_journal *j) {
1815 if (!j)
1816 return;
8725d60a
LP
1817
1818 j->current_field = 0;
c2373f84 1819}
50f20cfd 1820
a5344d2c 1821_public_ int sd_journal_get_fd(sd_journal *j) {
a963990f
LP
1822 int r;
1823
a5344d2c
LP
1824 if (!j)
1825 return -EINVAL;
50f20cfd 1826
a963990f
LP
1827 if (j->inotify_fd >= 0)
1828 return j->inotify_fd;
1829
1830 r = allocate_inotify(j);
1831 if (r < 0)
1832 return r;
1833
1834 /* Iterate through all dirs again, to add them to the
1835 * inotify */
7827b1a1
LP
1836 if (j->path)
1837 r = add_root_directory(j, j->path);
1838 else
1839 r = add_search_paths(j);
a963990f
LP
1840 if (r < 0)
1841 return r;
1842
50f20cfd
LP
1843 return j->inotify_fd;
1844}
1845
1846static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
a963990f 1847 Directory *d;
50f20cfd
LP
1848 int r;
1849
1850 assert(j);
1851 assert(e);
1852
1853 /* Is this a subdirectory we watch? */
a963990f
LP
1854 d = hashmap_get(j->directories_by_wd, INT_TO_PTR(e->wd));
1855 if (d) {
1856 sd_id128_t id;
50f20cfd 1857
de2c3907
LP
1858 if (!(e->mask & IN_ISDIR) && e->len > 0 &&
1859 (endswith(e->name, ".journal") ||
1860 endswith(e->name, ".journal~"))) {
50f20cfd
LP
1861
1862 /* Event for a journal file */
1863
1864 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
a963990f 1865 r = add_file(j, d->path, e->name);
50f20cfd 1866 if (r < 0)
a963990f
LP
1867 log_debug("Failed to add file %s/%s: %s", d->path, e->name, strerror(-r));
1868
50f20cfd
LP
1869 } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1870
a963990f 1871 r = remove_file(j, d->path, e->name);
50f20cfd 1872 if (r < 0)
a963990f 1873 log_debug("Failed to remove file %s/%s: %s", d->path, e->name, strerror(-r));
50f20cfd
LP
1874 }
1875
a963990f 1876 } else if (!d->is_root && e->len == 0) {
50f20cfd 1877
a963990f 1878 /* Event for a subdirectory */
50f20cfd 1879
a963990f
LP
1880 if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) {
1881 r = remove_directory(j, d);
50f20cfd 1882 if (r < 0)
a963990f 1883 log_debug("Failed to remove directory %s: %s", d->path, strerror(-r));
50f20cfd
LP
1884 }
1885
50f20cfd 1886
a963990f 1887 } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
50f20cfd 1888
a963990f 1889 /* Event for root directory */
50f20cfd 1890
a963990f
LP
1891 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1892 r = add_directory(j, d->path, e->name);
50f20cfd 1893 if (r < 0)
a963990f 1894 log_debug("Failed to add directory %s/%s: %s", d->path, e->name, strerror(-r));
50f20cfd
LP
1895 }
1896 }
1897
1898 return;
1899 }
1900
1901 if (e->mask & IN_IGNORED)
1902 return;
1903
1904 log_warning("Unknown inotify event.");
1905}
1906
a963990f
LP
1907static int determine_change(sd_journal *j) {
1908 bool b;
1909
1910 assert(j);
1911
1912 b = j->current_invalidate_counter != j->last_invalidate_counter;
1913 j->last_invalidate_counter = j->current_invalidate_counter;
1914
1915 return b ? SD_JOURNAL_INVALIDATE : SD_JOURNAL_APPEND;
1916}
1917
a5344d2c 1918_public_ int sd_journal_process(sd_journal *j) {
19d1e4ee 1919 uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX] _alignas_(struct inotify_event);
a963990f 1920 bool got_something = false;
50f20cfd 1921
a5344d2c
LP
1922 if (!j)
1923 return -EINVAL;
50f20cfd
LP
1924
1925 for (;;) {
1926 struct inotify_event *e;
1927 ssize_t l;
1928
1929 l = read(j->inotify_fd, buffer, sizeof(buffer));
1930 if (l < 0) {
a963990f
LP
1931 if (errno == EAGAIN || errno == EINTR)
1932 return got_something ? determine_change(j) : SD_JOURNAL_NOP;
50f20cfd
LP
1933
1934 return -errno;
1935 }
1936
a963990f
LP
1937 got_something = true;
1938
50f20cfd
LP
1939 e = (struct inotify_event*) buffer;
1940 while (l > 0) {
1941 size_t step;
1942
1943 process_inotify_event(j, e);
1944
1945 step = sizeof(struct inotify_event) + e->len;
1946 assert(step <= (size_t) l);
1947
1948 e = (struct inotify_event*) ((uint8_t*) e + step);
1949 l -= step;
1950 }
1951 }
a963990f
LP
1952
1953 return determine_change(j);
50f20cfd 1954}
6ad1d1c3 1955
e02d1cf7 1956_public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) {
a963990f 1957 int r;
e02d1cf7
LP
1958
1959 assert(j);
1960
a963990f
LP
1961 if (j->inotify_fd < 0) {
1962
1963 /* This is the first invocation, hence create the
1964 * inotify watch */
1965 r = sd_journal_get_fd(j);
1966 if (r < 0)
1967 return r;
1968
1969 /* The journal might have changed since the context
1970 * object was created and we weren't watching before,
1971 * hence don't wait for anything, and return
1972 * immediately. */
1973 return determine_change(j);
1974 }
1975
1976 do {
1977 r = fd_wait_for_event(j->inotify_fd, POLLIN, timeout_usec);
1978 } while (r == -EINTR);
e02d1cf7
LP
1979
1980 if (r < 0)
1981 return r;
1982
a963990f 1983 return sd_journal_process(j);
e02d1cf7
LP
1984}
1985
08984293
LP
1986_public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) {
1987 Iterator i;
1988 JournalFile *f;
1989 bool first = true;
1990 int r;
1991
1992 if (!j)
1993 return -EINVAL;
1994 if (!from && !to)
1995 return -EINVAL;
1996
1997 HASHMAP_FOREACH(f, j->files, i) {
1998 usec_t fr, t;
1999
2000 r = journal_file_get_cutoff_realtime_usec(f, &fr, &t);
9f8d2983
LP
2001 if (r == -ENOENT)
2002 continue;
08984293
LP
2003 if (r < 0)
2004 return r;
2005 if (r == 0)
2006 continue;
2007
2008 if (first) {
2009 if (from)
2010 *from = fr;
2011 if (to)
2012 *to = t;
2013 first = false;
2014 } else {
2015 if (from)
2016 *from = MIN(fr, *from);
2017 if (to)
2018 *to = MIN(t, *to);
2019 }
2020 }
2021
2022 return first ? 0 : 1;
2023}
2024
2025_public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) {
2026 Iterator i;
2027 JournalFile *f;
2028 bool first = true;
2029 int r;
2030
2031 if (!j)
2032 return -EINVAL;
2033 if (!from && !to)
2034 return -EINVAL;
2035
2036 HASHMAP_FOREACH(f, j->files, i) {
2037 usec_t fr, t;
2038
2039 r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t);
9f8d2983
LP
2040 if (r == -ENOENT)
2041 continue;
08984293
LP
2042 if (r < 0)
2043 return r;
2044 if (r == 0)
2045 continue;
2046
2047 if (first) {
2048 if (from)
2049 *from = fr;
2050 if (to)
2051 *to = t;
2052 first = false;
2053 } else {
2054 if (from)
2055 *from = MIN(fr, *from);
2056 if (to)
2057 *to = MIN(t, *to);
2058 }
2059 }
2060
2061 return first ? 0 : 1;
2062}
2063
dca6219e
LP
2064void journal_print_header(sd_journal *j) {
2065 Iterator i;
2066 JournalFile *f;
2067 bool newline = false;
2068
2069 assert(j);
2070
2071 HASHMAP_FOREACH(f, j->files, i) {
2072 if (newline)
2073 putchar('\n');
2074 else
2075 newline = true;
2076
2077 journal_file_print_header(f);
2078 }
2079}
08984293 2080
a1a03e30
LP
2081_public_ int sd_journal_get_usage(sd_journal *j, uint64_t *bytes) {
2082 Iterator i;
2083 JournalFile *f;
2084 uint64_t sum = 0;
2085
2086 if (!j)
2087 return -EINVAL;
2088 if (!bytes)
2089 return -EINVAL;
2090
2091 HASHMAP_FOREACH(f, j->files, i) {
2092 struct stat st;
2093
2094 if (fstat(f->fd, &st) < 0)
2095 return -errno;
2096
2097 sum += (uint64_t) st.st_blocks * 512ULL;
2098 }
2099
2100 *bytes = sum;
2101 return 0;
2102}
2103
19a2bd80
LP
2104/* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */
2105/* if (!j) */
2106/* return -EINVAL; */
2107/* if (!field) */
2108/* return -EINVAL; */
2109
2110/* return -ENOTSUP; */
2111/* } */
2112
2113/* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */
2114/* if (!j) */
2115/* return -EINVAL; */
2116/* if (!data) */
2117/* return -EINVAL; */
2118/* if (!l) */
2119/* return -EINVAL; */
2120
2121/* return -ENOTSUP; */
2122/* } */
2123
2124/* _public_ void sd_journal_restart_unique(sd_journal *j) { */
2125/* if (!j) */
2126/* return; */
2127/* } */