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