chiark / gitweb /
9c6cbaac5108856aacdc05188d9e256e855355d2
[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                         return r;
675                 else if (r == 0)
676                         continue;
677
678                 if (!new_current)
679                         found = true;
680                 else {
681                         int k;
682
683                         k = compare_order(f, o, new_current, new_entry);
684
685                         if (direction == DIRECTION_DOWN)
686                                 found = k < 0;
687                         else
688                                 found = k > 0;
689                 }
690
691                 if (found) {
692                         new_current = f;
693                         new_entry = o;
694                         new_offset = p;
695                 }
696         }
697
698         if (!new_current)
699                 return 0;
700
701         set_location(j, new_current, new_entry, new_offset);
702
703         return 1;
704 }
705
706 _public_ int sd_journal_next(sd_journal *j) {
707         return real_journal_next(j, DIRECTION_DOWN);
708 }
709
710 _public_ int sd_journal_previous(sd_journal *j) {
711         return real_journal_next(j, DIRECTION_UP);
712 }
713
714 static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
715         int c = 0, r;
716
717         if (!j)
718                 return -EINVAL;
719
720         if (skip == 0) {
721                 /* If this is not a discrete skip, then at least
722                  * resolve the current location */
723                 if (j->current_location.type != LOCATION_DISCRETE)
724                         return real_journal_next(j, direction);
725
726                 return 0;
727         }
728
729         do {
730                 r = real_journal_next(j, direction);
731                 if (r < 0)
732                         return r;
733
734                 if (r == 0)
735                         return c;
736
737                 skip--;
738                 c++;
739         } while (skip > 0);
740
741         return c;
742 }
743
744 _public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
745         return real_journal_next_skip(j, DIRECTION_DOWN, skip);
746 }
747
748 _public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
749         return real_journal_next_skip(j, DIRECTION_UP, skip);
750 }
751
752 _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
753         Object *o;
754         int r;
755         char bid[33], sid[33];
756
757         if (!j)
758                 return -EINVAL;
759         if (!cursor)
760                 return -EINVAL;
761
762         if (!j->current_file || j->current_file->current_offset <= 0)
763                 return -EADDRNOTAVAIL;
764
765         r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
766         if (r < 0)
767                 return r;
768
769         sd_id128_to_string(j->current_file->header->seqnum_id, sid);
770         sd_id128_to_string(o->entry.boot_id, bid);
771
772         if (asprintf(cursor,
773                      "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
774                      sid, (unsigned long long) le64toh(o->entry.seqnum),
775                      bid, (unsigned long long) le64toh(o->entry.monotonic),
776                      (unsigned long long) le64toh(o->entry.realtime),
777                      (unsigned long long) le64toh(o->entry.xor_hash),
778                      path_get_file_name(j->current_file->path)) < 0)
779                 return -ENOMEM;
780
781         return 1;
782 }
783
784 _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
785         char *w;
786         size_t l;
787         char *state;
788         unsigned long long seqnum, monotonic, realtime, xor_hash;
789         bool
790                 seqnum_id_set = false,
791                 seqnum_set = false,
792                 boot_id_set = false,
793                 monotonic_set = false,
794                 realtime_set = false,
795                 xor_hash_set = false;
796         sd_id128_t seqnum_id, boot_id;
797
798         if (!j)
799                 return -EINVAL;
800         if (!cursor)
801                 return -EINVAL;
802
803         FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
804                 char *item;
805                 int k = 0;
806
807                 if (l < 2 || w[1] != '=')
808                         return -EINVAL;
809
810                 item = strndup(w, l);
811                 if (!item)
812                         return -ENOMEM;
813
814                 switch (w[0]) {
815
816                 case 's':
817                         seqnum_id_set = true;
818                         k = sd_id128_from_string(w+2, &seqnum_id);
819                         break;
820
821                 case 'i':
822                         seqnum_set = true;
823                         if (sscanf(w+2, "%llx", &seqnum) != 1)
824                                 k = -EINVAL;
825                         break;
826
827                 case 'b':
828                         boot_id_set = true;
829                         k = sd_id128_from_string(w+2, &boot_id);
830                         break;
831
832                 case 'm':
833                         monotonic_set = true;
834                         if (sscanf(w+2, "%llx", &monotonic) != 1)
835                                 k = -EINVAL;
836                         break;
837
838                 case 't':
839                         realtime_set = true;
840                         if (sscanf(w+2, "%llx", &realtime) != 1)
841                                 k = -EINVAL;
842                         break;
843
844                 case 'x':
845                         xor_hash_set = true;
846                         if (sscanf(w+2, "%llx", &xor_hash) != 1)
847                                 k = -EINVAL;
848                         break;
849                 }
850
851                 free(item);
852
853                 if (k < 0)
854                         return k;
855         }
856
857         if ((!seqnum_set || !seqnum_id_set) &&
858             (!monotonic_set || !boot_id_set) &&
859             !realtime_set)
860                 return -EINVAL;
861
862         reset_location(j);
863
864         j->current_location.type = LOCATION_DISCRETE;
865
866         if (realtime_set) {
867                 j->current_location.realtime = (uint64_t) realtime;
868                 j->current_location.realtime_set = true;
869         }
870
871         if (seqnum_set && seqnum_id_set) {
872                 j->current_location.seqnum = (uint64_t) seqnum;
873                 j->current_location.seqnum_id = seqnum_id;
874                 j->current_location.seqnum_set = true;
875         }
876
877         if (monotonic_set && boot_id_set) {
878                 j->current_location.monotonic = (uint64_t) monotonic;
879                 j->current_location.boot_id = boot_id;
880                 j->current_location.monotonic_set = true;
881         }
882
883         if (xor_hash_set) {
884                 j->current_location.xor_hash = (uint64_t) xor_hash;
885                 j->current_location.xor_hash_set = true;
886         }
887
888         return 0;
889 }
890
891 _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
892         if (!j)
893                 return -EINVAL;
894
895         reset_location(j);
896         j->current_location.type = LOCATION_DISCRETE;
897         j->current_location.boot_id = boot_id;
898         j->current_location.monotonic = usec;
899         j->current_location.monotonic_set = true;
900
901         return 0;
902 }
903
904 _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
905         if (!j)
906                 return -EINVAL;
907
908         reset_location(j);
909         j->current_location.type = LOCATION_DISCRETE;
910         j->current_location.realtime = usec;
911         j->current_location.realtime_set = true;
912
913         return 0;
914 }
915
916 _public_ int sd_journal_seek_head(sd_journal *j) {
917         if (!j)
918                 return -EINVAL;
919
920         reset_location(j);
921         j->current_location.type = LOCATION_HEAD;
922
923         return 0;
924 }
925
926 _public_ int sd_journal_seek_tail(sd_journal *j) {
927         if (!j)
928                 return -EINVAL;
929
930         reset_location(j);
931         j->current_location.type = LOCATION_TAIL;
932
933         return 0;
934 }
935
936 static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
937         char *fn;
938         int r;
939         JournalFile *f;
940
941         assert(j);
942         assert(prefix);
943         assert(filename);
944
945         if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) &&
946             !(streq(filename, "system.journal") ||
947              (startswith(filename, "system@") && endswith(filename, ".journal"))))
948                 return 0;
949
950         if (dir)
951                 fn = join(prefix, "/", dir, "/", filename, NULL);
952         else
953                 fn = join(prefix, "/", filename, NULL);
954
955         if (!fn)
956                 return -ENOMEM;
957
958         if (hashmap_get(j->files, fn)) {
959                 free(fn);
960                 return 0;
961         }
962
963         if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
964                 log_debug("Too many open journal files, not adding %s, ignoring.", fn);
965                 free(fn);
966                 return 0;
967         }
968
969         r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
970         free(fn);
971
972         if (r < 0) {
973                 if (errno == ENOENT)
974                         return 0;
975
976                 return r;
977         }
978
979         /* journal_file_dump(f); */
980
981         r = hashmap_put(j->files, f->path, f);
982         if (r < 0) {
983                 journal_file_close(f);
984                 return r;
985         }
986
987         log_debug("File %s got added.", f->path);
988
989         return 0;
990 }
991
992 static int remove_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
993         char *fn;
994         JournalFile *f;
995
996         assert(j);
997         assert(prefix);
998         assert(filename);
999
1000         if (dir)
1001                 fn = join(prefix, "/", dir, "/", filename, NULL);
1002         else
1003                 fn = join(prefix, "/", filename, NULL);
1004
1005         if (!fn)
1006                 return -ENOMEM;
1007
1008         f = hashmap_get(j->files, fn);
1009         free(fn);
1010
1011         if (!f)
1012                 return 0;
1013
1014         hashmap_remove(j->files, f->path);
1015         journal_file_close(f);
1016
1017         log_debug("File %s got removed.", f->path);
1018         return 0;
1019 }
1020
1021 static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
1022         char *fn;
1023         int r;
1024         DIR *d;
1025         int wd;
1026         sd_id128_t id, mid;
1027
1028         assert(j);
1029         assert(prefix);
1030         assert(dir);
1031
1032         if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
1033             (sd_id128_from_string(dir, &id) < 0 ||
1034              sd_id128_get_machine(&mid) < 0 ||
1035              !sd_id128_equal(id, mid)))
1036             return 0;
1037
1038         fn = join(prefix, "/", dir, NULL);
1039         if (!fn)
1040                 return -ENOMEM;
1041
1042         d = opendir(fn);
1043
1044         if (!d) {
1045                 free(fn);
1046                 if (errno == ENOENT)
1047                         return 0;
1048
1049                 return -errno;
1050         }
1051
1052         wd = inotify_add_watch(j->inotify_fd, fn,
1053                                IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1054                                IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|
1055                                IN_DONT_FOLLOW|IN_ONLYDIR);
1056         if (wd > 0) {
1057                 if (hashmap_put(j->inotify_wd_dirs, INT_TO_PTR(wd), fn) < 0)
1058                         inotify_rm_watch(j->inotify_fd, wd);
1059                 else
1060                         fn = NULL;
1061         }
1062
1063         free(fn);
1064
1065         for (;;) {
1066                 struct dirent buf, *de;
1067
1068                 r = readdir_r(d, &buf, &de);
1069                 if (r != 0 || !de)
1070                         break;
1071
1072                 if (!dirent_is_file_with_suffix(de, ".journal"))
1073                         continue;
1074
1075                 r = add_file(j, prefix, dir, de->d_name);
1076                 if (r < 0)
1077                         log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
1078         }
1079
1080         closedir(d);
1081
1082         log_debug("Directory %s/%s got added.", prefix, dir);
1083
1084         return 0;
1085 }
1086
1087 static void remove_directory_wd(sd_journal *j, int wd) {
1088         char *p;
1089
1090         assert(j);
1091         assert(wd > 0);
1092
1093         if (j->inotify_fd >= 0)
1094                 inotify_rm_watch(j->inotify_fd, wd);
1095
1096         p = hashmap_remove(j->inotify_wd_dirs, INT_TO_PTR(wd));
1097
1098         if (p) {
1099                 log_debug("Directory %s got removed.", p);
1100                 free(p);
1101         }
1102 }
1103
1104 static void add_root_wd(sd_journal *j, const char *p) {
1105         int wd;
1106         char *k;
1107
1108         assert(j);
1109         assert(p);
1110
1111         wd = inotify_add_watch(j->inotify_fd, p,
1112                                IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1113                                IN_DONT_FOLLOW|IN_ONLYDIR);
1114         if (wd <= 0)
1115                 return;
1116
1117         k = strdup(p);
1118         if (!k || hashmap_put(j->inotify_wd_roots, INT_TO_PTR(wd), k) < 0) {
1119                 inotify_rm_watch(j->inotify_fd, wd);
1120                 free(k);
1121         }
1122 }
1123
1124 static void remove_root_wd(sd_journal *j, int wd) {
1125         char *p;
1126
1127         assert(j);
1128         assert(wd > 0);
1129
1130         if (j->inotify_fd >= 0)
1131                 inotify_rm_watch(j->inotify_fd, wd);
1132
1133         p = hashmap_remove(j->inotify_wd_roots, INT_TO_PTR(wd));
1134
1135         if (p) {
1136                 log_debug("Root %s got removed.", p);
1137                 free(p);
1138         }
1139 }
1140
1141 _public_ int sd_journal_open(sd_journal **ret, int flags) {
1142         sd_journal *j;
1143         const char *p;
1144         const char search_paths[] =
1145                 "/run/log/journal\0"
1146                 "/var/log/journal\0";
1147         int r;
1148
1149         if (!ret)
1150                 return -EINVAL;
1151
1152         if (flags & ~(SD_JOURNAL_LOCAL_ONLY|
1153                       SD_JOURNAL_RUNTIME_ONLY|
1154                       SD_JOURNAL_SYSTEM_ONLY))
1155                 return -EINVAL;
1156
1157         j = new0(sd_journal, 1);
1158         if (!j)
1159                 return -ENOMEM;
1160
1161         j->flags = flags;
1162
1163         j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
1164         if (j->inotify_fd < 0) {
1165                 r = -errno;
1166                 goto fail;
1167         }
1168
1169         j->files = hashmap_new(string_hash_func, string_compare_func);
1170         if (!j->files) {
1171                 r = -ENOMEM;
1172                 goto fail;
1173         }
1174
1175         j->inotify_wd_dirs = hashmap_new(trivial_hash_func, trivial_compare_func);
1176         j->inotify_wd_roots = hashmap_new(trivial_hash_func, trivial_compare_func);
1177
1178         if (!j->inotify_wd_dirs || !j->inotify_wd_roots) {
1179                 r = -ENOMEM;
1180                 goto fail;
1181         }
1182
1183         /* We ignore most errors here, since the idea is to only open
1184          * what's actually accessible, and ignore the rest. */
1185
1186         NULSTR_FOREACH(p, search_paths) {
1187                 DIR *d;
1188
1189                 if ((flags & SD_JOURNAL_RUNTIME_ONLY) &&
1190                     !path_startswith(p, "/run"))
1191                         continue;
1192
1193                 d = opendir(p);
1194                 if (!d) {
1195                         if (errno != ENOENT)
1196                                 log_debug("Failed to open %s: %m", p);
1197                         continue;
1198                 }
1199
1200                 add_root_wd(j, p);
1201
1202                 for (;;) {
1203                         struct dirent buf, *de;
1204                         sd_id128_t id;
1205
1206                         r = readdir_r(d, &buf, &de);
1207                         if (r != 0 || !de)
1208                                 break;
1209
1210                         if (dirent_is_file_with_suffix(de, ".journal")) {
1211                                 r = add_file(j, p, NULL, de->d_name);
1212                                 if (r < 0)
1213                                         log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
1214
1215                         } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
1216                                    sd_id128_from_string(de->d_name, &id) >= 0) {
1217
1218                                 r = add_directory(j, p, de->d_name);
1219                                 if (r < 0)
1220                                         log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
1221                         }
1222                 }
1223
1224                 closedir(d);
1225         }
1226
1227         *ret = j;
1228         return 0;
1229
1230 fail:
1231         sd_journal_close(j);
1232
1233         return r;
1234 };
1235
1236 _public_ void sd_journal_close(sd_journal *j) {
1237         if (!j)
1238                 return;
1239
1240         if (j->inotify_wd_dirs) {
1241                 void *k;
1242
1243                 while ((k = hashmap_first_key(j->inotify_wd_dirs)))
1244                         remove_directory_wd(j, PTR_TO_INT(k));
1245
1246                 hashmap_free(j->inotify_wd_dirs);
1247         }
1248
1249         if (j->inotify_wd_roots) {
1250                 void *k;
1251
1252                 while ((k = hashmap_first_key(j->inotify_wd_roots)))
1253                         remove_root_wd(j, PTR_TO_INT(k));
1254
1255                 hashmap_free(j->inotify_wd_roots);
1256         }
1257
1258         if (j->files) {
1259                 JournalFile *f;
1260
1261                 while ((f = hashmap_steal_first(j->files)))
1262                         journal_file_close(f);
1263
1264                 hashmap_free(j->files);
1265         }
1266
1267         sd_journal_flush_matches(j);
1268
1269         if (j->inotify_fd >= 0)
1270                 close_nointr_nofail(j->inotify_fd);
1271
1272         free(j);
1273 }
1274
1275 _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
1276         Object *o;
1277         JournalFile *f;
1278         int r;
1279
1280         if (!j)
1281                 return -EINVAL;
1282         if (!ret)
1283                 return -EINVAL;
1284
1285         f = j->current_file;
1286         if (!f)
1287                 return -EADDRNOTAVAIL;
1288
1289         if (f->current_offset <= 0)
1290                 return -EADDRNOTAVAIL;
1291
1292         r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1293         if (r < 0)
1294                 return r;
1295
1296         *ret = le64toh(o->entry.realtime);
1297         return 0;
1298 }
1299
1300 _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
1301         Object *o;
1302         JournalFile *f;
1303         int r;
1304         sd_id128_t id;
1305
1306         if (!j)
1307                 return -EINVAL;
1308         if (!ret)
1309                 return -EINVAL;
1310
1311         f = j->current_file;
1312         if (!f)
1313                 return -EADDRNOTAVAIL;
1314
1315         if (f->current_offset <= 0)
1316                 return -EADDRNOTAVAIL;
1317
1318         r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1319         if (r < 0)
1320                 return r;
1321
1322         if (ret_boot_id)
1323                 *ret_boot_id = o->entry.boot_id;
1324         else {
1325                 r = sd_id128_get_boot(&id);
1326                 if (r < 0)
1327                         return r;
1328
1329                 if (!sd_id128_equal(id, o->entry.boot_id))
1330                         return -ESTALE;
1331         }
1332
1333         *ret = le64toh(o->entry.monotonic);
1334         return 0;
1335 }
1336
1337 _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
1338         JournalFile *f;
1339         uint64_t i, n;
1340         size_t field_length;
1341         int r;
1342         Object *o;
1343
1344         if (!j)
1345                 return -EINVAL;
1346         if (!field)
1347                 return -EINVAL;
1348         if (!data)
1349                 return -EINVAL;
1350         if (!size)
1351                 return -EINVAL;
1352
1353         if (isempty(field) || strchr(field, '='))
1354                 return -EINVAL;
1355
1356         f = j->current_file;
1357         if (!f)
1358                 return -EADDRNOTAVAIL;
1359
1360         if (f->current_offset <= 0)
1361                 return -EADDRNOTAVAIL;
1362
1363         r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1364         if (r < 0)
1365                 return r;
1366
1367         field_length = strlen(field);
1368
1369         n = journal_file_entry_n_items(o);
1370         for (i = 0; i < n; i++) {
1371                 uint64_t p, l;
1372                 le64_t le_hash;
1373                 size_t t;
1374
1375                 p = le64toh(o->entry.items[i].object_offset);
1376                 le_hash = o->entry.items[i].hash;
1377                 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1378                 if (r < 0)
1379                         return r;
1380
1381                 if (le_hash != o->data.hash)
1382                         return -EBADMSG;
1383
1384                 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1385
1386                 if (o->object.flags & OBJECT_COMPRESSED) {
1387
1388 #ifdef HAVE_XZ
1389                         if (uncompress_startswith(o->data.payload, l,
1390                                                   &f->compress_buffer, &f->compress_buffer_size,
1391                                                   field, field_length, '=')) {
1392
1393                                 uint64_t rsize;
1394
1395                                 if (!uncompress_blob(o->data.payload, l,
1396                                                      &f->compress_buffer, &f->compress_buffer_size, &rsize))
1397                                         return -EBADMSG;
1398
1399                                 *data = f->compress_buffer;
1400                                 *size = (size_t) rsize;
1401
1402                                 return 0;
1403                         }
1404 #else
1405                         return -EPROTONOSUPPORT;
1406 #endif
1407
1408                 } else if (l >= field_length+1 &&
1409                            memcmp(o->data.payload, field, field_length) == 0 &&
1410                            o->data.payload[field_length] == '=') {
1411
1412                         t = (size_t) l;
1413
1414                         if ((uint64_t) t != l)
1415                                 return -E2BIG;
1416
1417                         *data = o->data.payload;
1418                         *size = t;
1419
1420                         return 0;
1421                 }
1422
1423                 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1424                 if (r < 0)
1425                         return r;
1426         }
1427
1428         return -ENOENT;
1429 }
1430
1431 _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
1432         JournalFile *f;
1433         uint64_t p, l, n;
1434         le64_t le_hash;
1435         int r;
1436         Object *o;
1437         size_t t;
1438
1439         if (!j)
1440                 return -EINVAL;
1441         if (!data)
1442                 return -EINVAL;
1443         if (!size)
1444                 return -EINVAL;
1445
1446         f = j->current_file;
1447         if (!f)
1448                 return -EADDRNOTAVAIL;
1449
1450         if (f->current_offset <= 0)
1451                 return -EADDRNOTAVAIL;
1452
1453         r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1454         if (r < 0)
1455                 return r;
1456
1457         n = journal_file_entry_n_items(o);
1458         if (j->current_field >= n)
1459                 return 0;
1460
1461         p = le64toh(o->entry.items[j->current_field].object_offset);
1462         le_hash = o->entry.items[j->current_field].hash;
1463         r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1464         if (r < 0)
1465                 return r;
1466
1467         if (le_hash != o->data.hash)
1468                 return -EBADMSG;
1469
1470         l = le64toh(o->object.size) - offsetof(Object, data.payload);
1471         t = (size_t) l;
1472
1473         /* We can't read objects larger than 4G on a 32bit machine */
1474         if ((uint64_t) t != l)
1475                 return -E2BIG;
1476
1477         if (o->object.flags & OBJECT_COMPRESSED) {
1478 #ifdef HAVE_XZ
1479                 uint64_t rsize;
1480
1481                 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
1482                         return -EBADMSG;
1483
1484                 *data = f->compress_buffer;
1485                 *size = (size_t) rsize;
1486 #else
1487                 return -EPROTONOSUPPORT;
1488 #endif
1489         } else {
1490                 *data = o->data.payload;
1491                 *size = t;
1492         }
1493
1494         j->current_field ++;
1495
1496         return 1;
1497 }
1498
1499 _public_ void sd_journal_restart_data(sd_journal *j) {
1500         if (!j)
1501                 return;
1502
1503         j->current_field = 0;
1504 }
1505
1506 _public_ int sd_journal_get_fd(sd_journal *j) {
1507         if (!j)
1508                 return -EINVAL;
1509
1510         return j->inotify_fd;
1511 }
1512
1513 static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
1514         char *p;
1515         int r;
1516
1517         assert(j);
1518         assert(e);
1519
1520         /* Is this a subdirectory we watch? */
1521         p = hashmap_get(j->inotify_wd_dirs, INT_TO_PTR(e->wd));
1522         if (p) {
1523
1524                 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
1525
1526                         /* Event for a journal file */
1527
1528                         if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1529                                 r = add_file(j, p, NULL, e->name);
1530                                 if (r < 0)
1531                                         log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
1532                         } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1533
1534                                 r = remove_file(j, p, NULL, e->name);
1535                                 if (r < 0)
1536                                         log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
1537                         }
1538
1539                 } else if (e->len == 0) {
1540
1541                         /* Event for the directory itself */
1542
1543                         if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT))
1544                                 remove_directory_wd(j, e->wd);
1545                 }
1546
1547                 return;
1548         }
1549
1550         /* Must be the root directory then? */
1551         p = hashmap_get(j->inotify_wd_roots, INT_TO_PTR(e->wd));
1552         if (p) {
1553                 sd_id128_t id;
1554
1555                 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
1556
1557                         /* Event for a journal file */
1558
1559                         if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1560                                 r = add_file(j, p, NULL, e->name);
1561                                 if (r < 0)
1562                                         log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r));
1563                         } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1564
1565                                 r = remove_file(j, p, NULL, e->name);
1566                                 if (r < 0)
1567                                         log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r));
1568                         }
1569
1570                 } else if ((e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
1571
1572                         /* Event for subdirectory */
1573
1574                         if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1575
1576                                 r = add_directory(j, p, e->name);
1577                                 if (r < 0)
1578                                         log_debug("Failed to add directory %s/%s: %s", p, e->name, strerror(-r));
1579                         }
1580                 }
1581
1582                 return;
1583         }
1584
1585         if (e->mask & IN_IGNORED)
1586                 return;
1587
1588         log_warning("Unknown inotify event.");
1589 }
1590
1591 _public_ int sd_journal_process(sd_journal *j) {
1592         uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX];
1593
1594         if (!j)
1595                 return -EINVAL;
1596
1597         for (;;) {
1598                 struct inotify_event *e;
1599                 ssize_t l;
1600
1601                 l = read(j->inotify_fd, buffer, sizeof(buffer));
1602                 if (l < 0) {
1603                         if (errno == EINTR || errno == EAGAIN)
1604                                 return 0;
1605
1606                         return -errno;
1607                 }
1608
1609                 e = (struct inotify_event*) buffer;
1610                 while (l > 0) {
1611                         size_t step;
1612
1613                         process_inotify_event(j, e);
1614
1615                         step = sizeof(struct inotify_event) + e->len;
1616                         assert(step <= (size_t) l);
1617
1618                         e = (struct inotify_event*) ((uint8_t*) e + step);
1619                         l -= step;
1620                 }
1621         }
1622 }
1623
1624 _public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) {
1625         Iterator i;
1626         JournalFile *f;
1627         bool first = true;
1628         int r;
1629
1630         if (!j)
1631                 return -EINVAL;
1632         if (!from && !to)
1633                 return -EINVAL;
1634
1635         HASHMAP_FOREACH(f, j->files, i) {
1636                 usec_t fr, t;
1637
1638                 r = journal_file_get_cutoff_realtime_usec(f, &fr, &t);
1639                 if (r < 0)
1640                         return r;
1641                 if (r == 0)
1642                         continue;
1643
1644                 if (first) {
1645                         if (from)
1646                                 *from = fr;
1647                         if (to)
1648                                 *to = t;
1649                         first = false;
1650                 } else {
1651                         if (from)
1652                                 *from = MIN(fr, *from);
1653                         if (to)
1654                                 *to = MIN(t, *to);
1655                 }
1656         }
1657
1658         return first ? 0 : 1;
1659 }
1660
1661 _public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) {
1662         Iterator i;
1663         JournalFile *f;
1664         bool first = true;
1665         int r;
1666
1667         if (!j)
1668                 return -EINVAL;
1669         if (!from && !to)
1670                 return -EINVAL;
1671
1672         HASHMAP_FOREACH(f, j->files, i) {
1673                 usec_t fr, t;
1674
1675                 r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t);
1676                 if (r < 0)
1677                         return r;
1678                 if (r == 0)
1679                         continue;
1680
1681                 if (first) {
1682                         if (from)
1683                                 *from = fr;
1684                         if (to)
1685                                 *to = t;
1686                         first = false;
1687                 } else {
1688                         if (from)
1689                                 *from = MIN(fr, *from);
1690                         if (to)
1691                                 *to = MIN(t, *to);
1692                 }
1693         }
1694
1695         return first ? 0 : 1;
1696 }
1697
1698
1699 /* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */
1700 /*         if (!j) */
1701 /*                 return -EINVAL; */
1702 /*         if (!field) */
1703 /*                 return -EINVAL; */
1704
1705 /*         return -ENOTSUP; */
1706 /* } */
1707
1708 /* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */
1709 /*         if (!j) */
1710 /*                 return -EINVAL; */
1711 /*         if (!data) */
1712 /*                 return -EINVAL; */
1713 /*         if (!l) */
1714 /*                 return -EINVAL; */
1715
1716 /*         return -ENOTSUP; */
1717 /* } */
1718
1719 /* _public_ void sd_journal_restart_unique(sd_journal *j) { */
1720 /*         if (!j) */
1721 /*                 return; */
1722 /* } */