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