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