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