chiark / gitweb /
75884594a19346819e683748d2fb65e6e6f2288d
[elogind.git] / src / journal / sd-journal.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stddef.h>
25 #include <unistd.h>
26 #include <sys/inotify.h>
27
28 #include "sd-journal.h"
29 #include "journal-def.h"
30 #include "journal-file.h"
31 #include "hashmap.h"
32 #include "list.h"
33 #include "path-util.h"
34 #include "lookup3.h"
35 #include "compress.h"
36 #include "journal-internal.h"
37
38 #define JOURNAL_FILES_MAX 1024
39
40 static void detach_location(sd_journal *j) {
41         Iterator i;
42         JournalFile *f;
43
44         assert(j);
45
46         j->current_file = NULL;
47         j->current_field = 0;
48
49         HASHMAP_FOREACH(f, j->files, i)
50                 f->current_offset = 0;
51 }
52
53 static void reset_location(sd_journal *j) {
54         assert(j);
55
56         detach_location(j);
57         zero(j->current_location);
58 }
59
60 static void init_location(Location *l, JournalFile *f, Object *o) {
61         assert(l);
62         assert(f);
63         assert(o->object.type == OBJECT_ENTRY);
64
65         l->type = LOCATION_DISCRETE;
66         l->seqnum = le64toh(o->entry.seqnum);
67         l->seqnum_id = f->header->seqnum_id;
68         l->realtime = le64toh(o->entry.realtime);
69         l->monotonic = le64toh(o->entry.monotonic);
70         l->boot_id = o->entry.boot_id;
71         l->xor_hash = le64toh(o->entry.xor_hash);
72
73         l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true;
74 }
75
76 static void set_location(sd_journal *j, JournalFile *f, Object *o, uint64_t offset) {
77         assert(j);
78         assert(f);
79         assert(o);
80
81         init_location(&j->current_location, f, o);
82
83         j->current_file = f;
84         j->current_field = 0;
85
86         f->current_offset = offset;
87 }
88
89 static int same_field(const void *_a, size_t s, const void *_b, size_t t) {
90         const uint8_t *a = _a, *b = _b;
91         size_t j;
92         bool a_good = false, b_good = false, different = false;
93
94         for (j = 0; j < s && j < t; j++) {
95
96                 if (a[j] == '=')
97                         a_good = true;
98                 if (b[j] == '=')
99                         b_good = true;
100                 if (a[j] != b[j])
101                         different = true;
102
103                 if (a_good && b_good)
104                         return different ? 0 : 1;
105         }
106
107         return -EINVAL;
108 }
109
110 _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
111         Match *m, *after = NULL;
112         le64_t le_hash;
113
114         if (!j)
115                 return -EINVAL;
116         if (!data)
117                 return -EINVAL;
118         if (size <= 1)
119                 return -EINVAL;
120         if (!memchr(data, '=', size))
121                 return -EINVAL;
122         if (*(char*) data == '=')
123                 return -EINVAL;
124
125         /* FIXME: iterating with multiple matches is currently
126          * broken */
127         if (j->matches)
128                 return -ENOTSUP;
129
130         le_hash = htole64(hash64(data, size));
131
132         LIST_FOREACH(matches, m, j->matches) {
133                 int r;
134
135                 if (m->le_hash == le_hash &&
136                     m->size == size &&
137                     memcmp(m->data, data, size) == 0)
138                         return 0;
139
140                 r = same_field(data, size, m->data, m->size);
141                 if (r < 0)
142                         return r;
143                 else if (r > 0)
144                         after = m;
145         }
146
147         m = new0(Match, 1);
148         if (!m)
149                 return -ENOMEM;
150
151         m->size = size;
152
153         m->data = malloc(m->size);
154         if (!m->data) {
155                 free(m);
156                 return -ENOMEM;
157         }
158
159         memcpy(m->data, data, size);
160         m->le_hash = le_hash;
161
162         /* Matches for the same fields we order adjacent to each
163          * other */
164         LIST_INSERT_AFTER(Match, matches, j->matches, after, m);
165         j->n_matches ++;
166
167         detach_location(j);
168
169         return 0;
170 }
171
172 _public_ void sd_journal_flush_matches(sd_journal *j) {
173         if (!j)
174                 return;
175
176         while (j->matches) {
177                 Match *m = j->matches;
178
179                 LIST_REMOVE(Match, matches, j->matches, m);
180                 free(m->data);
181                 free(m);
182         }
183
184         j->n_matches = 0;
185
186         detach_location(j);
187 }
188
189 static int compare_order(JournalFile *af, Object *ao,
190                          JournalFile *bf, Object *bo) {
191
192         uint64_t a, b;
193
194         assert(af);
195         assert(ao);
196         assert(bf);
197         assert(bo);
198
199         /* We operate on two different files here, hence we can access
200          * two objects at the same time, which we normally can't.
201          *
202          * If contents and timestamps match, these entries are
203          * identical, even if the seqnum does not match */
204
205         if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
206             ao->entry.monotonic == bo->entry.monotonic &&
207             ao->entry.realtime == bo->entry.realtime &&
208             ao->entry.xor_hash == bo->entry.xor_hash)
209                 return 0;
210
211         if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
212
213                 /* If this is from the same seqnum source, compare
214                  * seqnums */
215                 a = le64toh(ao->entry.seqnum);
216                 b = le64toh(bo->entry.seqnum);
217
218                 if (a < b)
219                         return -1;
220                 if (a > b)
221                         return 1;
222
223                 /* Wow! This is weird, different data but the same
224                  * seqnums? Something is borked, but let's make the
225                  * best of it and compare by time. */
226         }
227
228         if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
229
230                 /* If the boot id matches compare monotonic time */
231                 a = le64toh(ao->entry.monotonic);
232                 b = le64toh(bo->entry.monotonic);
233
234                 if (a < b)
235                         return -1;
236                 if (a > b)
237                         return 1;
238         }
239
240         /* Otherwise compare UTC time */
241         a = le64toh(ao->entry.realtime);
242         b = le64toh(ao->entry.realtime);
243
244         if (a < b)
245                 return -1;
246         if (a > b)
247                 return 1;
248
249         /* Finally, compare by contents */
250         a = le64toh(ao->entry.xor_hash);
251         b = le64toh(ao->entry.xor_hash);
252
253         if (a < b)
254                 return -1;
255         if (a > b)
256                 return 1;
257
258         return 0;
259 }
260
261 static int compare_with_location(JournalFile *af, Object *ao, Location *l) {
262         uint64_t a;
263
264         assert(af);
265         assert(ao);
266         assert(l);
267         assert(l->type == LOCATION_DISCRETE);
268
269         if (l->monotonic_set &&
270             sd_id128_equal(ao->entry.boot_id, l->boot_id) &&
271             l->realtime_set &&
272             le64toh(ao->entry.realtime) == l->realtime &&
273             l->xor_hash_set &&
274             le64toh(ao->entry.xor_hash) == l->xor_hash)
275                 return 0;
276
277         if (l->seqnum_set &&
278             sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) {
279
280                 a = le64toh(ao->entry.seqnum);
281
282                 if (a < l->seqnum)
283                         return -1;
284                 if (a > l->seqnum)
285                         return 1;
286         }
287
288         if (l->monotonic_set &&
289             sd_id128_equal(ao->entry.boot_id, l->boot_id)) {
290
291                 a = le64toh(ao->entry.monotonic);
292
293                 if (a < l->monotonic)
294                         return -1;
295                 if (a > l->monotonic)
296                         return 1;
297         }
298
299         if (l->realtime_set) {
300
301                 a = le64toh(ao->entry.realtime);
302
303                 if (a < l->realtime)
304                         return -1;
305                 if (a > l->realtime)
306                         return 1;
307         }
308
309         if (l->xor_hash_set) {
310                 a = le64toh(ao->entry.xor_hash);
311
312                 if (a < l->xor_hash)
313                         return -1;
314                 if (a > l->xor_hash)
315                         return 1;
316         }
317
318         return 0;
319 }
320
321 static int find_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
322         Object *o = NULL;
323         uint64_t p = 0;
324         int r;
325
326         assert(j);
327
328         if (!j->matches) {
329                 /* No matches is simple */
330
331                 if (j->current_location.type == LOCATION_HEAD)
332                         r = journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p);
333                 else if (j->current_location.type == LOCATION_TAIL)
334                         r = journal_file_next_entry(f, NULL, 0, DIRECTION_UP, &o, &p);
335                 else if (j->current_location.seqnum_set &&
336                          sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
337                         r = journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, &o, &p);
338                 else if (j->current_location.monotonic_set) {
339                         r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
340
341                         if (r == -ENOENT) {
342                                 /* boot id unknown in this file */
343                                 if (j->current_location.realtime_set)
344                                         r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
345                                 else
346                                         r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
347                         }
348                 } else if (j->current_location.realtime_set)
349                         r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
350                 else
351                         r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
352
353                 if (r <= 0)
354                         return r;
355
356         } else  {
357                 Match *m, *term_match = NULL;
358                 Object *to = NULL;
359                 uint64_t tp = 0;
360
361                 /* We have matches, first, let's jump to the monotonic
362                  * position if we have any, since it implies a
363                  * match. */
364
365                 if (j->current_location.type == LOCATION_DISCRETE &&
366                     j->current_location.monotonic_set) {
367
368                         r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
369                         if (r <= 0)
370                                 return r == -ENOENT ? 0 : r;
371                 }
372
373                 LIST_FOREACH(matches, m, j->matches) {
374                         Object *c, *d;
375                         uint64_t cp, dp;
376
377                         r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), &d, &dp);
378                         if (r <= 0)
379                                 return r;
380
381                         if (j->current_location.type == LOCATION_HEAD)
382                                 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, &c, &cp);
383                         else if (j->current_location.type == LOCATION_TAIL)
384                                 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, &c, &cp);
385                         else if (j->current_location.seqnum_set &&
386                                  sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
387                                 r = journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, &c, &cp);
388                         else if (j->current_location.realtime_set)
389                                 r = journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, &c, &cp);
390                         else
391                                 r = journal_file_next_entry_for_data(f, NULL, 0, dp, direction, &c, &cp);
392
393                         if (r < 0)
394                                 return r;
395
396                         if (!term_match) {
397                                 term_match = m;
398
399                                 if (r > 0) {
400                                         to = c;
401                                         tp = cp;
402                                 }
403                         } else if (same_field(term_match->data, term_match->size, m->data, m->size)) {
404
405                                 /* Same field as previous match... */
406                                 if (r > 0) {
407
408                                         /* Find the earliest of the OR matches */
409
410                                         if (!to ||
411                                             (direction == DIRECTION_DOWN && cp < tp) ||
412                                             (direction == DIRECTION_UP && cp > tp)) {
413                                                 to = c;
414                                                 tp = cp;
415                                         }
416
417                                 }
418
419                         } else {
420
421                                 /* Previous term is finished, did anything match? */
422                                 if (!to)
423                                         return 0;
424
425                                 /* Find the last of the AND matches */
426                                 if (!o ||
427                                     (direction == DIRECTION_DOWN && tp > p) ||
428                                     (direction == DIRECTION_UP && tp < p)) {
429                                         o = to;
430                                         p = tp;
431                                 }
432
433                                 term_match = m;
434
435                                 if (r > 0) {
436                                         to = c;
437                                         tp = cp;
438                                 } else {
439                                         to = NULL;
440                                         tp = 0;
441                                 }
442                         }
443                 }
444
445                 /* Last term is finished, did anything match? */
446                 if (!to)
447                         return 0;
448
449                 if (!o ||
450                     (direction == DIRECTION_DOWN && tp > p) ||
451                     (direction == DIRECTION_UP && tp < p)) {
452                         o = to;
453                         p = tp;
454                 }
455
456                 if (!o)
457                         return 0;
458         }
459
460         if (ret)
461                 *ret = o;
462
463         if (offset)
464                 *offset = p;
465
466         return 1;
467 }
468
469 static int next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
470         int r;
471         uint64_t cp;
472         Object *c;
473
474         assert(j);
475         assert(f);
476         assert(ret);
477         assert(offset);
478
479         c = *ret;
480         cp = *offset;
481
482         if (!j->matches) {
483                 /* No matches is easy */
484
485                 r = journal_file_next_entry(f, c, cp, direction, &c, &cp);
486                 if (r <= 0)
487                         return r;
488
489                 if (ret)
490                         *ret = c;
491                 if (offset)
492                         *offset = cp;
493                 return 1;
494         }
495
496         /* So there are matches we have to adhere to, let's find the
497          * first entry that matches all of them */
498
499         for (;;) {
500                 uint64_t np, n;
501                 bool found, term_result = false;
502                 Match *m, *term_match = NULL;
503                 Object *npo = NULL;
504
505                 n = journal_file_entry_n_items(c);
506
507                 /* Make sure we don't match the entry we are starting
508                  * from. */
509                 found = cp != *offset;
510
511                 np = 0;
512                 LIST_FOREACH(matches, m, j->matches) {
513                         uint64_t q, k;
514                         Object *qo = NULL;
515
516                         /* Let's check if this is the beginning of a
517                          * new term, i.e. has a different field prefix
518                          * as the preceeding match. */
519                         if (!term_match) {
520                                 term_match = m;
521                                 term_result = false;
522                         } else if (!same_field(term_match->data, term_match->size, m->data, m->size)) {
523                                 if (!term_result)
524                                         found = false;
525
526                                 term_match = m;
527                                 term_result = false;
528                         }
529
530                         for (k = 0; k < n; k++)
531                                 if (c->entry.items[k].hash == m->le_hash)
532                                         break;
533
534                         if (k >= n) {
535                                 /* Hmm, didn't find any field that
536                                  * matched this rule, so ignore this
537                                  * match. Go on with next match */
538                                 continue;
539                         }
540
541                         term_result = true;
542
543                         /* Hmm, so, this field matched, let's remember
544                          * where we'd have to try next, in case the other
545                          * matches are not OK */
546
547                         r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, &qo, &q);
548                         /* This pointer is invalidated if the window was
549                          * remapped. May need to re-fetch it later */
550                         c = NULL;
551                         if (r < 0)
552                                 return r;
553
554                         if (r > 0) {
555
556                                 if (direction == DIRECTION_DOWN) {
557                                         if (q > np) {
558                                                 np = q;
559                                                 npo = qo;
560                                         }
561                                 } else {
562                                         if (np == 0 || q < np) {
563                                                 np = q;
564                                                 npo = qo;
565                                         }
566                                 }
567                         }
568                 }
569
570                 /* Check the last term */
571                 if (term_match && !term_result)
572                         found = false;
573
574                 /* Did this entry match against all matches? */
575                 if (found) {
576                         if (ret) {
577                                 if (c == NULL) {
578                                         /* Re-fetch the entry */
579                                         r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
580                                         if (r < 0)
581                                                 return r;
582                                 }
583                                 *ret = c;
584                         }
585                         if (offset)
586                                 *offset = cp;
587                         return 1;
588                 }
589
590                 /* Did we find a subsequent entry? */
591                 if (np == 0)
592                         return 0;
593
594                 /* Hmm, ok, this entry only matched partially, so
595                  * let's try another one */
596                 cp = np;
597                 c = npo;
598         }
599 }
600
601 static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
602         Object *c;
603         uint64_t cp;
604         int compare_value, r;
605
606         assert(j);
607         assert(f);
608
609         if (f->current_offset > 0) {
610                 cp = f->current_offset;
611
612                 r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
613                 if (r < 0)
614                         return r;
615
616                 r = next_with_matches(j, f, direction, &c, &cp);
617                 if (r <= 0)
618                         return r;
619
620                 compare_value = 1;
621         } else {
622                 r = find_location(j, f, direction, &c, &cp);
623                 if (r <= 0)
624                         return r;
625
626                 compare_value = 0;
627         }
628
629         for (;;) {
630                 bool found;
631
632                 if (j->current_location.type == LOCATION_DISCRETE) {
633                         int k;
634
635                         k = compare_with_location(f, c, &j->current_location);
636                         if (direction == DIRECTION_DOWN)
637                                 found = k >= compare_value;
638                         else
639                                 found = k <= -compare_value;
640                 } else
641                         found = true;
642
643                 if (found) {
644                         if (ret)
645                                 *ret = c;
646                         if (offset)
647                                 *offset = cp;
648                         return 1;
649                 }
650
651                 r = next_with_matches(j, f, direction, &c, &cp);
652                 if (r <= 0)
653                         return r;
654         }
655 }
656
657 static int real_journal_next(sd_journal *j, direction_t direction) {
658         JournalFile *f, *new_current = NULL;
659         Iterator i;
660         int r;
661         uint64_t new_offset = 0;
662         Object *new_entry = NULL;
663
664         if (!j)
665                 return -EINVAL;
666
667         HASHMAP_FOREACH(f, j->files, i) {
668                 Object *o;
669                 uint64_t p;
670                 bool found;
671
672                 r = next_beyond_location(j, f, direction, &o, &p);
673                 if (r < 0) {
674                         log_debug("Can't iterate through %s, ignoring: %s", f->path, strerror(-r));
675                         continue;
676                 } else if (r == 0)
677                         continue;
678
679                 if (!new_current)
680                         found = true;
681                 else {
682                         int k;
683
684                         k = compare_order(f, o, new_current, new_entry);
685
686                         if (direction == DIRECTION_DOWN)
687                                 found = k < 0;
688                         else
689                                 found = k > 0;
690                 }
691
692                 if (found) {
693                         new_current = f;
694                         new_entry = o;
695                         new_offset = p;
696                 }
697         }
698
699         if (!new_current)
700                 return 0;
701
702         set_location(j, new_current, new_entry, new_offset);
703
704         return 1;
705 }
706
707 _public_ int sd_journal_next(sd_journal *j) {
708         return real_journal_next(j, DIRECTION_DOWN);
709 }
710
711 _public_ int sd_journal_previous(sd_journal *j) {
712         return real_journal_next(j, DIRECTION_UP);
713 }
714
715 static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
716         int c = 0, r;
717
718         if (!j)
719                 return -EINVAL;
720
721         if (skip == 0) {
722                 /* If this is not a discrete skip, then at least
723                  * resolve the current location */
724                 if (j->current_location.type != LOCATION_DISCRETE)
725                         return real_journal_next(j, direction);
726
727                 return 0;
728         }
729
730         do {
731                 r = real_journal_next(j, direction);
732                 if (r < 0)
733                         return r;
734
735                 if (r == 0)
736                         return c;
737
738                 skip--;
739                 c++;
740         } while (skip > 0);
741
742         return c;
743 }
744
745 _public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
746         return real_journal_next_skip(j, DIRECTION_DOWN, skip);
747 }
748
749 _public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
750         return real_journal_next_skip(j, DIRECTION_UP, skip);
751 }
752
753 _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
754         Object *o;
755         int r;
756         char bid[33], sid[33];
757
758         if (!j)
759                 return -EINVAL;
760         if (!cursor)
761                 return -EINVAL;
762
763         if (!j->current_file || j->current_file->current_offset <= 0)
764                 return -EADDRNOTAVAIL;
765
766         r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
767         if (r < 0)
768                 return r;
769
770         sd_id128_to_string(j->current_file->header->seqnum_id, sid);
771         sd_id128_to_string(o->entry.boot_id, bid);
772
773         if (asprintf(cursor,
774                      "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
775                      sid, (unsigned long long) le64toh(o->entry.seqnum),
776                      bid, (unsigned long long) le64toh(o->entry.monotonic),
777                      (unsigned long long) le64toh(o->entry.realtime),
778                      (unsigned long long) le64toh(o->entry.xor_hash),
779                      path_get_file_name(j->current_file->path)) < 0)
780                 return -ENOMEM;
781
782         return 1;
783 }
784
785 _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
786         char *w;
787         size_t l;
788         char *state;
789         unsigned long long seqnum, monotonic, realtime, xor_hash;
790         bool
791                 seqnum_id_set = false,
792                 seqnum_set = false,
793                 boot_id_set = false,
794                 monotonic_set = false,
795                 realtime_set = false,
796                 xor_hash_set = false;
797         sd_id128_t seqnum_id, boot_id;
798
799         if (!j)
800                 return -EINVAL;
801         if (!cursor)
802                 return -EINVAL;
803
804         FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
805                 char *item;
806                 int k = 0;
807
808                 if (l < 2 || w[1] != '=')
809                         return -EINVAL;
810
811                 item = strndup(w, l);
812                 if (!item)
813                         return -ENOMEM;
814
815                 switch (w[0]) {
816
817                 case 's':
818                         seqnum_id_set = true;
819                         k = sd_id128_from_string(w+2, &seqnum_id);
820                         break;
821
822                 case 'i':
823                         seqnum_set = true;
824                         if (sscanf(w+2, "%llx", &seqnum) != 1)
825                                 k = -EINVAL;
826                         break;
827
828                 case 'b':
829                         boot_id_set = true;
830                         k = sd_id128_from_string(w+2, &boot_id);
831                         break;
832
833                 case 'm':
834                         monotonic_set = true;
835                         if (sscanf(w+2, "%llx", &monotonic) != 1)
836                                 k = -EINVAL;
837                         break;
838
839                 case 't':
840                         realtime_set = true;
841                         if (sscanf(w+2, "%llx", &realtime) != 1)
842                                 k = -EINVAL;
843                         break;
844
845                 case 'x':
846                         xor_hash_set = true;
847                         if (sscanf(w+2, "%llx", &xor_hash) != 1)
848                                 k = -EINVAL;
849                         break;
850                 }
851
852                 free(item);
853
854                 if (k < 0)
855                         return k;
856         }
857
858         if ((!seqnum_set || !seqnum_id_set) &&
859             (!monotonic_set || !boot_id_set) &&
860             !realtime_set)
861                 return -EINVAL;
862
863         reset_location(j);
864
865         j->current_location.type = LOCATION_DISCRETE;
866
867         if (realtime_set) {
868                 j->current_location.realtime = (uint64_t) realtime;
869                 j->current_location.realtime_set = true;
870         }
871
872         if (seqnum_set && seqnum_id_set) {
873                 j->current_location.seqnum = (uint64_t) seqnum;
874                 j->current_location.seqnum_id = seqnum_id;
875                 j->current_location.seqnum_set = true;
876         }
877
878         if (monotonic_set && boot_id_set) {
879                 j->current_location.monotonic = (uint64_t) monotonic;
880                 j->current_location.boot_id = boot_id;
881                 j->current_location.monotonic_set = true;
882         }
883
884         if (xor_hash_set) {
885                 j->current_location.xor_hash = (uint64_t) xor_hash;
886                 j->current_location.xor_hash_set = true;
887         }
888
889         return 0;
890 }
891
892 _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
893         if (!j)
894                 return -EINVAL;
895
896         reset_location(j);
897         j->current_location.type = LOCATION_DISCRETE;
898         j->current_location.boot_id = boot_id;
899         j->current_location.monotonic = usec;
900         j->current_location.monotonic_set = true;
901
902         return 0;
903 }
904
905 _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
906         if (!j)
907                 return -EINVAL;
908
909         reset_location(j);
910         j->current_location.type = LOCATION_DISCRETE;
911         j->current_location.realtime = usec;
912         j->current_location.realtime_set = true;
913
914         return 0;
915 }
916
917 _public_ int sd_journal_seek_head(sd_journal *j) {
918         if (!j)
919                 return -EINVAL;
920
921         reset_location(j);
922         j->current_location.type = LOCATION_HEAD;
923
924         return 0;
925 }
926
927 _public_ int sd_journal_seek_tail(sd_journal *j) {
928         if (!j)
929                 return -EINVAL;
930
931         reset_location(j);
932         j->current_location.type = LOCATION_TAIL;
933
934         return 0;
935 }
936
937 static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
938         char *fn;
939         int r;
940         JournalFile *f;
941
942         assert(j);
943         assert(prefix);
944         assert(filename);
945
946         if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) &&
947             !(streq(filename, "system.journal") ||
948              (startswith(filename, "system@") && endswith(filename, ".journal"))))
949                 return 0;
950
951         if (dir)
952                 fn = join(prefix, "/", dir, "/", filename, NULL);
953         else
954                 fn = join(prefix, "/", filename, NULL);
955
956         if (!fn)
957                 return -ENOMEM;
958
959         if (hashmap_get(j->files, fn)) {
960                 free(fn);
961                 return 0;
962         }
963
964         if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
965                 log_debug("Too many open journal files, not adding %s, ignoring.", fn);
966                 free(fn);
967                 return 0;
968         }
969
970         r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
971         free(fn);
972
973         if (r < 0) {
974                 if (errno == ENOENT)
975                         return 0;
976
977                 return r;
978         }
979
980         /* journal_file_dump(f); */
981
982         r = hashmap_put(j->files, f->path, f);
983         if (r < 0) {
984                 journal_file_close(f);
985                 return r;
986         }
987
988         log_debug("File %s got added.", f->path);
989
990         return 0;
991 }
992
993 static int remove_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
994         char *fn;
995         JournalFile *f;
996
997         assert(j);
998         assert(prefix);
999         assert(filename);
1000
1001         if (dir)
1002                 fn = join(prefix, "/", dir, "/", filename, NULL);
1003         else
1004                 fn = join(prefix, "/", filename, NULL);
1005
1006         if (!fn)
1007                 return -ENOMEM;
1008
1009         f = hashmap_get(j->files, fn);
1010         free(fn);
1011
1012         if (!f)
1013                 return 0;
1014
1015         hashmap_remove(j->files, f->path);
1016         journal_file_close(f);
1017
1018         log_debug("File %s got removed.", f->path);
1019         return 0;
1020 }
1021
1022 static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
1023         char *fn;
1024         int r;
1025         DIR *d;
1026         int wd;
1027         sd_id128_t id, mid;
1028
1029         assert(j);
1030         assert(prefix);
1031         assert(dir);
1032
1033         if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
1034             (sd_id128_from_string(dir, &id) < 0 ||
1035              sd_id128_get_machine(&mid) < 0 ||
1036              !sd_id128_equal(id, mid)))
1037             return 0;
1038
1039         fn = join(prefix, "/", dir, NULL);
1040         if (!fn)
1041                 return -ENOMEM;
1042
1043         d = opendir(fn);
1044
1045         if (!d) {
1046                 free(fn);
1047                 if (errno == ENOENT)
1048                         return 0;
1049
1050                 return -errno;
1051         }
1052
1053         wd = inotify_add_watch(j->inotify_fd, fn,
1054                                IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1055                                IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|
1056                                IN_DONT_FOLLOW|IN_ONLYDIR);
1057         if (wd > 0) {
1058                 if (hashmap_put(j->inotify_wd_dirs, INT_TO_PTR(wd), fn) < 0)
1059                         inotify_rm_watch(j->inotify_fd, wd);
1060                 else
1061                         fn = NULL;
1062         }
1063
1064         free(fn);
1065
1066         for (;;) {
1067                 struct dirent buf, *de;
1068
1069                 r = readdir_r(d, &buf, &de);
1070                 if (r != 0 || !de)
1071                         break;
1072
1073                 if (!dirent_is_file_with_suffix(de, ".journal"))
1074                         continue;
1075
1076                 r = add_file(j, prefix, dir, de->d_name);
1077                 if (r < 0)
1078                         log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
1079         }
1080
1081         closedir(d);
1082
1083         log_debug("Directory %s/%s got added.", prefix, dir);
1084
1085         return 0;
1086 }
1087
1088 static void remove_directory_wd(sd_journal *j, int wd) {
1089         char *p;
1090
1091         assert(j);
1092         assert(wd > 0);
1093
1094         if (j->inotify_fd >= 0)
1095                 inotify_rm_watch(j->inotify_fd, wd);
1096
1097         p = hashmap_remove(j->inotify_wd_dirs, INT_TO_PTR(wd));
1098
1099         if (p) {
1100                 log_debug("Directory %s got removed.", p);
1101                 free(p);
1102         }
1103 }
1104
1105 static void add_root_wd(sd_journal *j, const char *p) {
1106         int wd;
1107         char *k;
1108
1109         assert(j);
1110         assert(p);
1111
1112         wd = inotify_add_watch(j->inotify_fd, p,
1113                                IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1114                                IN_DONT_FOLLOW|IN_ONLYDIR);
1115         if (wd <= 0)
1116                 return;
1117
1118         k = strdup(p);
1119         if (!k || hashmap_put(j->inotify_wd_roots, INT_TO_PTR(wd), k) < 0) {
1120                 inotify_rm_watch(j->inotify_fd, wd);
1121                 free(k);
1122         }
1123 }
1124
1125 static void remove_root_wd(sd_journal *j, int wd) {
1126         char *p;
1127
1128         assert(j);
1129         assert(wd > 0);
1130
1131         if (j->inotify_fd >= 0)
1132                 inotify_rm_watch(j->inotify_fd, wd);
1133
1134         p = hashmap_remove(j->inotify_wd_roots, INT_TO_PTR(wd));
1135
1136         if (p) {
1137                 log_debug("Root %s got removed.", p);
1138                 free(p);
1139         }
1140 }
1141
1142 _public_ int sd_journal_open(sd_journal **ret, int flags) {
1143         sd_journal *j;
1144         const char *p;
1145         const char search_paths[] =
1146                 "/run/log/journal\0"
1147                 "/var/log/journal\0";
1148         int r;
1149
1150         if (!ret)
1151                 return -EINVAL;
1152
1153         if (flags & ~(SD_JOURNAL_LOCAL_ONLY|
1154                       SD_JOURNAL_RUNTIME_ONLY|
1155                       SD_JOURNAL_SYSTEM_ONLY))
1156                 return -EINVAL;
1157
1158         j = new0(sd_journal, 1);
1159         if (!j)
1160                 return -ENOMEM;
1161
1162         j->flags = flags;
1163
1164         j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
1165         if (j->inotify_fd < 0) {
1166                 r = -errno;
1167                 goto fail;
1168         }
1169
1170         j->files = hashmap_new(string_hash_func, string_compare_func);
1171         if (!j->files) {
1172                 r = -ENOMEM;
1173                 goto fail;
1174         }
1175
1176         j->inotify_wd_dirs = hashmap_new(trivial_hash_func, trivial_compare_func);
1177         j->inotify_wd_roots = hashmap_new(trivial_hash_func, trivial_compare_func);
1178
1179         if (!j->inotify_wd_dirs || !j->inotify_wd_roots) {
1180                 r = -ENOMEM;
1181                 goto fail;
1182         }
1183
1184         /* We ignore most errors here, since the idea is to only open
1185          * what's actually accessible, and ignore the rest. */
1186
1187         NULSTR_FOREACH(p, search_paths) {
1188                 DIR *d;
1189
1190                 if ((flags & SD_JOURNAL_RUNTIME_ONLY) &&
1191                     !path_startswith(p, "/run"))
1192                         continue;
1193
1194                 d = opendir(p);
1195                 if (!d) {
1196                         if (errno != ENOENT)
1197                                 log_debug("Failed to open %s: %m", p);
1198                         continue;
1199                 }
1200
1201                 add_root_wd(j, p);
1202
1203                 for (;;) {
1204                         struct dirent buf, *de;
1205                         sd_id128_t id;
1206
1207                         r = readdir_r(d, &buf, &de);
1208                         if (r != 0 || !de)
1209                                 break;
1210
1211                         if (dirent_is_file_with_suffix(de, ".journal")) {
1212                                 r = add_file(j, p, NULL, de->d_name);
1213                                 if (r < 0)
1214                                         log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
1215
1216                         } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
1217                                    sd_id128_from_string(de->d_name, &id) >= 0) {
1218
1219                                 r = add_directory(j, p, de->d_name);
1220                                 if (r < 0)
1221                                         log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
1222                         }
1223                 }
1224
1225                 closedir(d);
1226         }
1227
1228         *ret = j;
1229         return 0;
1230
1231 fail:
1232         sd_journal_close(j);
1233
1234         return r;
1235 };
1236
1237 _public_ void sd_journal_close(sd_journal *j) {
1238         if (!j)
1239                 return;
1240
1241         if (j->inotify_wd_dirs) {
1242                 void *k;
1243
1244                 while ((k = hashmap_first_key(j->inotify_wd_dirs)))
1245                         remove_directory_wd(j, PTR_TO_INT(k));
1246
1247                 hashmap_free(j->inotify_wd_dirs);
1248         }
1249
1250         if (j->inotify_wd_roots) {
1251                 void *k;
1252
1253                 while ((k = hashmap_first_key(j->inotify_wd_roots)))
1254                         remove_root_wd(j, PTR_TO_INT(k));
1255
1256                 hashmap_free(j->inotify_wd_roots);
1257         }
1258
1259         if (j->files) {
1260                 JournalFile *f;
1261
1262                 while ((f = hashmap_steal_first(j->files)))
1263                         journal_file_close(f);
1264
1265                 hashmap_free(j->files);
1266         }
1267
1268         sd_journal_flush_matches(j);
1269
1270         if (j->inotify_fd >= 0)
1271                 close_nointr_nofail(j->inotify_fd);
1272
1273         free(j);
1274 }
1275
1276 _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
1277         Object *o;
1278         JournalFile *f;
1279         int r;
1280
1281         if (!j)
1282                 return -EINVAL;
1283         if (!ret)
1284                 return -EINVAL;
1285
1286         f = j->current_file;
1287         if (!f)
1288                 return -EADDRNOTAVAIL;
1289
1290         if (f->current_offset <= 0)
1291                 return -EADDRNOTAVAIL;
1292
1293         r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1294         if (r < 0)
1295                 return r;
1296
1297         *ret = le64toh(o->entry.realtime);
1298         return 0;
1299 }
1300
1301 _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
1302         Object *o;
1303         JournalFile *f;
1304         int r;
1305         sd_id128_t id;
1306
1307         if (!j)
1308                 return -EINVAL;
1309         if (!ret)
1310                 return -EINVAL;
1311
1312         f = j->current_file;
1313         if (!f)
1314                 return -EADDRNOTAVAIL;
1315
1316         if (f->current_offset <= 0)
1317                 return -EADDRNOTAVAIL;
1318
1319         r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1320         if (r < 0)
1321                 return r;
1322
1323         if (ret_boot_id)
1324                 *ret_boot_id = o->entry.boot_id;
1325         else {
1326                 r = sd_id128_get_boot(&id);
1327                 if (r < 0)
1328                         return r;
1329
1330                 if (!sd_id128_equal(id, o->entry.boot_id))
1331                         return -ESTALE;
1332         }
1333
1334         *ret = le64toh(o->entry.monotonic);
1335         return 0;
1336 }
1337
1338 _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
1339         JournalFile *f;
1340         uint64_t i, n;
1341         size_t field_length;
1342         int r;
1343         Object *o;
1344
1345         if (!j)
1346                 return -EINVAL;
1347         if (!field)
1348                 return -EINVAL;
1349         if (!data)
1350                 return -EINVAL;
1351         if (!size)
1352                 return -EINVAL;
1353
1354         if (isempty(field) || strchr(field, '='))
1355                 return -EINVAL;
1356
1357         f = j->current_file;
1358         if (!f)
1359                 return -EADDRNOTAVAIL;
1360
1361         if (f->current_offset <= 0)
1362                 return -EADDRNOTAVAIL;
1363
1364         r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1365         if (r < 0)
1366                 return r;
1367
1368         field_length = strlen(field);
1369
1370         n = journal_file_entry_n_items(o);
1371         for (i = 0; i < n; i++) {
1372                 uint64_t p, l;
1373                 le64_t le_hash;
1374                 size_t t;
1375
1376                 p = le64toh(o->entry.items[i].object_offset);
1377                 le_hash = o->entry.items[i].hash;
1378                 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1379                 if (r < 0)
1380                         return r;
1381
1382                 if (le_hash != o->data.hash)
1383                         return -EBADMSG;
1384
1385                 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1386
1387                 if (o->object.flags & OBJECT_COMPRESSED) {
1388
1389 #ifdef HAVE_XZ
1390                         if (uncompress_startswith(o->data.payload, l,
1391                                                   &f->compress_buffer, &f->compress_buffer_size,
1392                                                   field, field_length, '=')) {
1393
1394                                 uint64_t rsize;
1395
1396                                 if (!uncompress_blob(o->data.payload, l,
1397                                                      &f->compress_buffer, &f->compress_buffer_size, &rsize))
1398                                         return -EBADMSG;
1399
1400                                 *data = f->compress_buffer;
1401                                 *size = (size_t) rsize;
1402
1403                                 return 0;
1404                         }
1405 #else
1406                         return -EPROTONOSUPPORT;
1407 #endif
1408
1409                 } else if (l >= field_length+1 &&
1410                            memcmp(o->data.payload, field, field_length) == 0 &&
1411                            o->data.payload[field_length] == '=') {
1412
1413                         t = (size_t) l;
1414
1415                         if ((uint64_t) t != l)
1416                                 return -E2BIG;
1417
1418                         *data = o->data.payload;
1419                         *size = t;
1420
1421                         return 0;
1422                 }
1423
1424                 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1425                 if (r < 0)
1426                         return r;
1427         }
1428
1429         return -ENOENT;
1430 }
1431
1432 _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
1433         JournalFile *f;
1434         uint64_t p, l, n;
1435         le64_t le_hash;
1436         int r;
1437         Object *o;
1438         size_t t;
1439
1440         if (!j)
1441                 return -EINVAL;
1442         if (!data)
1443                 return -EINVAL;
1444         if (!size)
1445                 return -EINVAL;
1446
1447         f = j->current_file;
1448         if (!f)
1449                 return -EADDRNOTAVAIL;
1450
1451         if (f->current_offset <= 0)
1452                 return -EADDRNOTAVAIL;
1453
1454         r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1455         if (r < 0)
1456                 return r;
1457
1458         n = journal_file_entry_n_items(o);
1459         if (j->current_field >= n)
1460                 return 0;
1461
1462         p = le64toh(o->entry.items[j->current_field].object_offset);
1463         le_hash = o->entry.items[j->current_field].hash;
1464         r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1465         if (r < 0)
1466                 return r;
1467
1468         if (le_hash != o->data.hash)
1469                 return -EBADMSG;
1470
1471         l = le64toh(o->object.size) - offsetof(Object, data.payload);
1472         t = (size_t) l;
1473
1474         /* We can't read objects larger than 4G on a 32bit machine */
1475         if ((uint64_t) t != l)
1476                 return -E2BIG;
1477
1478         if (o->object.flags & OBJECT_COMPRESSED) {
1479 #ifdef HAVE_XZ
1480                 uint64_t rsize;
1481
1482                 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
1483                         return -EBADMSG;
1484
1485                 *data = f->compress_buffer;
1486                 *size = (size_t) rsize;
1487 #else
1488                 return -EPROTONOSUPPORT;
1489 #endif
1490         } else {
1491                 *data = o->data.payload;
1492                 *size = t;
1493         }
1494
1495         j->current_field ++;
1496
1497         return 1;
1498 }
1499
1500 _public_ void sd_journal_restart_data(sd_journal *j) {
1501         if (!j)
1502                 return;
1503
1504         j->current_field = 0;
1505 }
1506
1507 _public_ int sd_journal_get_fd(sd_journal *j) {
1508         if (!j)
1509                 return -EINVAL;
1510
1511         return j->inotify_fd;
1512 }
1513
1514 static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
1515         char *p;
1516         int r;
1517
1518         assert(j);
1519         assert(e);
1520
1521         /* Is this a subdirectory we watch? */
1522         p = hashmap_get(j->inotify_wd_dirs, INT_TO_PTR(e->wd));
1523         if (p) {
1524
1525                 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
1526
1527                         /* Event for a journal file */
1528
1529                         if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1530                                 r = add_file(j, p, NULL, e->name);
1531                                 if (r < 0)
1532                                         log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
1533                         } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1534
1535                                 r = remove_file(j, p, NULL, e->name);
1536                                 if (r < 0)
1537                                         log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
1538                         }
1539
1540                 } else if (e->len == 0) {
1541
1542                         /* Event for the directory itself */
1543
1544                         if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT))
1545                                 remove_directory_wd(j, e->wd);
1546                 }
1547
1548                 return;
1549         }
1550
1551         /* Must be the root directory then? */
1552         p = hashmap_get(j->inotify_wd_roots, INT_TO_PTR(e->wd));
1553         if (p) {
1554                 sd_id128_t id;
1555
1556                 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
1557
1558                         /* Event for a journal file */
1559
1560                         if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1561                                 r = add_file(j, p, NULL, e->name);
1562                                 if (r < 0)
1563                                         log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
1564                         } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1565
1566                                 r = remove_file(j, p, NULL, e->name);
1567                                 if (r < 0)
1568                                         log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
1569                         }
1570
1571                 } else if ((e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
1572
1573                         /* Event for subdirectory */
1574
1575                         if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1576
1577                                 r = add_directory(j, p, e->name);
1578                                 if (r < 0)
1579                                         log_debug("Failed to add directory %s/%s: %s", p, e->name, strerror(-r));
1580                         }
1581                 }
1582
1583                 return;
1584         }
1585
1586         if (e->mask & IN_IGNORED)
1587                 return;
1588
1589         log_warning("Unknown inotify event.");
1590 }
1591
1592 _public_ int sd_journal_process(sd_journal *j) {
1593         uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX];
1594
1595         if (!j)
1596                 return -EINVAL;
1597
1598         for (;;) {
1599                 struct inotify_event *e;
1600                 ssize_t l;
1601
1602                 l = read(j->inotify_fd, buffer, sizeof(buffer));
1603                 if (l < 0) {
1604                         if (errno == EINTR || errno == EAGAIN)
1605                                 return 0;
1606
1607                         return -errno;
1608                 }
1609
1610                 e = (struct inotify_event*) buffer;
1611                 while (l > 0) {
1612                         size_t step;
1613
1614                         process_inotify_event(j, e);
1615
1616                         step = sizeof(struct inotify_event) + e->len;
1617                         assert(step <= (size_t) l);
1618
1619                         e = (struct inotify_event*) ((uint8_t*) e + step);
1620                         l -= step;
1621                 }
1622         }
1623 }
1624
1625 _public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) {
1626         Iterator i;
1627         JournalFile *f;
1628         bool first = true;
1629         int r;
1630
1631         if (!j)
1632                 return -EINVAL;
1633         if (!from && !to)
1634                 return -EINVAL;
1635
1636         HASHMAP_FOREACH(f, j->files, i) {
1637                 usec_t fr, t;
1638
1639                 r = journal_file_get_cutoff_realtime_usec(f, &fr, &t);
1640                 if (r < 0)
1641                         return r;
1642                 if (r == 0)
1643                         continue;
1644
1645                 if (first) {
1646                         if (from)
1647                                 *from = fr;
1648                         if (to)
1649                                 *to = t;
1650                         first = false;
1651                 } else {
1652                         if (from)
1653                                 *from = MIN(fr, *from);
1654                         if (to)
1655                                 *to = MIN(t, *to);
1656                 }
1657         }
1658
1659         return first ? 0 : 1;
1660 }
1661
1662 _public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) {
1663         Iterator i;
1664         JournalFile *f;
1665         bool first = true;
1666         int r;
1667
1668         if (!j)
1669                 return -EINVAL;
1670         if (!from && !to)
1671                 return -EINVAL;
1672
1673         HASHMAP_FOREACH(f, j->files, i) {
1674                 usec_t fr, t;
1675
1676                 r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t);
1677                 if (r < 0)
1678                         return r;
1679                 if (r == 0)
1680                         continue;
1681
1682                 if (first) {
1683                         if (from)
1684                                 *from = fr;
1685                         if (to)
1686                                 *to = t;
1687                         first = false;
1688                 } else {
1689                         if (from)
1690                                 *from = MIN(fr, *from);
1691                         if (to)
1692                                 *to = MIN(t, *to);
1693                 }
1694         }
1695
1696         return first ? 0 : 1;
1697 }
1698
1699
1700 /* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */
1701 /*         if (!j) */
1702 /*                 return -EINVAL; */
1703 /*         if (!field) */
1704 /*                 return -EINVAL; */
1705
1706 /*         return -ENOTSUP; */
1707 /* } */
1708
1709 /* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */
1710 /*         if (!j) */
1711 /*                 return -EINVAL; */
1712 /*         if (!data) */
1713 /*                 return -EINVAL; */
1714 /*         if (!l) */
1715 /*                 return -EINVAL; */
1716
1717 /*         return -ENOTSUP; */
1718 /* } */
1719
1720 /* _public_ void sd_journal_restart_unique(sd_journal *j) { */
1721 /*         if (!j) */
1722 /*                 return; */
1723 /* } */