chiark / gitweb /
92ba57822bae3035595971da9ae08ca4aba7f41d
[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 General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU 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 /* } */