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