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