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