journal: check fields we search for more carefully
[systemd_ALT/systemd_imz.git] / src / journal / sd-journal.c
blobf4ef1aea7c92fd9e6716d3f1e3e7aa80f59c9996
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
3 /***
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
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.
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.
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 ***/
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stddef.h>
25 #include <unistd.h>
26 #include <sys/inotify.h>
27 #include <sys/poll.h>
29 #include "sd-journal.h"
30 #include "journal-def.h"
31 #include "journal-file.h"
32 #include "hashmap.h"
33 #include "list.h"
34 #include "path-util.h"
35 #include "lookup3.h"
36 #include "compress.h"
37 #include "journal-internal.h"
39 #define JOURNAL_FILES_MAX 1024
41 static void detach_location(sd_journal *j) {
42 Iterator i;
43 JournalFile *f;
45 assert(j);
47 j->current_file = NULL;
48 j->current_field = 0;
50 HASHMAP_FOREACH(f, j->files, i)
51 f->current_offset = 0;
54 static void reset_location(sd_journal *j) {
55 assert(j);
57 detach_location(j);
58 zero(j->current_location);
61 static void init_location(Location *l, JournalFile *f, Object *o) {
62 assert(l);
63 assert(f);
64 assert(o->object.type == OBJECT_ENTRY);
66 l->type = LOCATION_DISCRETE;
67 l->seqnum = le64toh(o->entry.seqnum);
68 l->seqnum_id = f->header->seqnum_id;
69 l->realtime = le64toh(o->entry.realtime);
70 l->monotonic = le64toh(o->entry.monotonic);
71 l->boot_id = o->entry.boot_id;
72 l->xor_hash = le64toh(o->entry.xor_hash);
74 l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true;
77 static void set_location(sd_journal *j, JournalFile *f, Object *o, uint64_t offset) {
78 assert(j);
79 assert(f);
80 assert(o);
82 init_location(&j->current_location, f, o);
84 j->current_file = f;
85 j->current_field = 0;
87 f->current_offset = offset;
90 static int same_field(const void *_a, size_t s, const void *_b, size_t t) {
91 const uint8_t *a = _a, *b = _b;
92 size_t j;
93 bool a_good = false, b_good = false, different = false;
95 for (j = 0; j < s && j < t; j++) {
97 if (a[j] == '=')
98 a_good = true;
99 if (b[j] == '=')
100 b_good = true;
101 if (a[j] != b[j])
102 different = true;
104 if (a_good && b_good)
105 return different ? 0 : 1;
108 return -EINVAL;
111 _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
112 Match *m, *after = NULL;
113 le64_t le_hash;
115 if (!j)
116 return -EINVAL;
117 if (!data)
118 return -EINVAL;
119 if (size <= 1)
120 return -EINVAL;
121 if (!memchr(data, '=', size))
122 return -EINVAL;
123 if (*(char*) data == '=')
124 return -EINVAL;
126 /* FIXME: iterating with multiple matches is currently
127 * broken */
128 if (j->matches)
129 return -ENOTSUP;
131 le_hash = htole64(hash64(data, size));
133 LIST_FOREACH(matches, m, j->matches) {
134 int r;
136 if (m->le_hash == le_hash &&
137 m->size == size &&
138 memcmp(m->data, data, size) == 0)
139 return 0;
141 r = same_field(data, size, m->data, m->size);
142 if (r < 0)
143 return r;
144 else if (r > 0)
145 after = m;
148 m = new0(Match, 1);
149 if (!m)
150 return -ENOMEM;
152 m->size = size;
154 m->data = malloc(m->size);
155 if (!m->data) {
156 free(m);
157 return -ENOMEM;
160 memcpy(m->data, data, size);
161 m->le_hash = le_hash;
163 /* Matches for the same fields we order adjacent to each
164 * other */
165 LIST_INSERT_AFTER(Match, matches, j->matches, after, m);
166 j->n_matches ++;
168 detach_location(j);
170 return 0;
173 _public_ void sd_journal_flush_matches(sd_journal *j) {
174 if (!j)
175 return;
177 while (j->matches) {
178 Match *m = j->matches;
180 LIST_REMOVE(Match, matches, j->matches, m);
181 free(m->data);
182 free(m);
185 j->n_matches = 0;
187 detach_location(j);
190 static int compare_order(JournalFile *af, Object *ao,
191 JournalFile *bf, Object *bo) {
193 uint64_t a, b;
195 assert(af);
196 assert(ao);
197 assert(bf);
198 assert(bo);
200 /* We operate on two different files here, hence we can access
201 * two objects at the same time, which we normally can't.
203 * If contents and timestamps match, these entries are
204 * identical, even if the seqnum does not match */
206 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
207 ao->entry.monotonic == bo->entry.monotonic &&
208 ao->entry.realtime == bo->entry.realtime &&
209 ao->entry.xor_hash == bo->entry.xor_hash)
210 return 0;
212 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
214 /* If this is from the same seqnum source, compare
215 * seqnums */
216 a = le64toh(ao->entry.seqnum);
217 b = le64toh(bo->entry.seqnum);
219 if (a < b)
220 return -1;
221 if (a > b)
222 return 1;
224 /* Wow! This is weird, different data but the same
225 * seqnums? Something is borked, but let's make the
226 * best of it and compare by time. */
229 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
231 /* If the boot id matches compare monotonic time */
232 a = le64toh(ao->entry.monotonic);
233 b = le64toh(bo->entry.monotonic);
235 if (a < b)
236 return -1;
237 if (a > b)
238 return 1;
241 /* Otherwise compare UTC time */
242 a = le64toh(ao->entry.realtime);
243 b = le64toh(bo->entry.realtime);
245 if (a < b)
246 return -1;
247 if (a > b)
248 return 1;
250 /* Finally, compare by contents */
251 a = le64toh(ao->entry.xor_hash);
252 b = le64toh(bo->entry.xor_hash);
254 if (a < b)
255 return -1;
256 if (a > b)
257 return 1;
259 return 0;
262 static int compare_with_location(JournalFile *af, Object *ao, Location *l) {
263 uint64_t a;
265 assert(af);
266 assert(ao);
267 assert(l);
268 assert(l->type == LOCATION_DISCRETE);
270 if (l->monotonic_set &&
271 sd_id128_equal(ao->entry.boot_id, l->boot_id) &&
272 l->realtime_set &&
273 le64toh(ao->entry.realtime) == l->realtime &&
274 l->xor_hash_set &&
275 le64toh(ao->entry.xor_hash) == l->xor_hash)
276 return 0;
278 if (l->seqnum_set &&
279 sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) {
281 a = le64toh(ao->entry.seqnum);
283 if (a < l->seqnum)
284 return -1;
285 if (a > l->seqnum)
286 return 1;
289 if (l->monotonic_set &&
290 sd_id128_equal(ao->entry.boot_id, l->boot_id)) {
292 a = le64toh(ao->entry.monotonic);
294 if (a < l->monotonic)
295 return -1;
296 if (a > l->monotonic)
297 return 1;
300 if (l->realtime_set) {
302 a = le64toh(ao->entry.realtime);
304 if (a < l->realtime)
305 return -1;
306 if (a > l->realtime)
307 return 1;
310 if (l->xor_hash_set) {
311 a = le64toh(ao->entry.xor_hash);
313 if (a < l->xor_hash)
314 return -1;
315 if (a > l->xor_hash)
316 return 1;
319 return 0;
322 static int find_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
323 Object *o = NULL;
324 uint64_t p = 0;
325 int r;
327 assert(j);
329 if (!j->matches) {
330 /* No matches is simple */
332 if (j->current_location.type == LOCATION_HEAD)
333 r = journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p);
334 else if (j->current_location.type == LOCATION_TAIL)
335 r = journal_file_next_entry(f, NULL, 0, DIRECTION_UP, &o, &p);
336 else if (j->current_location.seqnum_set &&
337 sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
338 r = journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, &o, &p);
339 else if (j->current_location.monotonic_set) {
340 r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
342 if (r == -ENOENT) {
343 /* boot id unknown in this file */
344 if (j->current_location.realtime_set)
345 r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
346 else
347 r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
349 } else if (j->current_location.realtime_set)
350 r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
351 else
352 r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
354 if (r <= 0)
355 return r;
357 } else {
358 Match *m, *term_match = NULL;
359 Object *to = NULL;
360 uint64_t tp = 0;
362 /* We have matches, first, let's jump to the monotonic
363 * position if we have any, since it implies a
364 * match. */
366 if (j->current_location.type == LOCATION_DISCRETE &&
367 j->current_location.monotonic_set) {
369 r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
370 if (r <= 0)
371 return r == -ENOENT ? 0 : r;
374 LIST_FOREACH(matches, m, j->matches) {
375 Object *c, *d;
376 uint64_t cp, dp;
378 r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), &d, &dp);
379 if (r <= 0)
380 return r;
382 if (j->current_location.type == LOCATION_HEAD)
383 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, &c, &cp);
384 else if (j->current_location.type == LOCATION_TAIL)
385 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, &c, &cp);
386 else if (j->current_location.seqnum_set &&
387 sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
388 r = journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, &c, &cp);
389 else if (j->current_location.realtime_set)
390 r = journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, &c, &cp);
391 else
392 r = journal_file_next_entry_for_data(f, NULL, 0, dp, direction, &c, &cp);
394 if (r < 0)
395 return r;
397 if (!term_match) {
398 term_match = m;
400 if (r > 0) {
401 to = c;
402 tp = cp;
404 } else if (same_field(term_match->data, term_match->size, m->data, m->size)) {
406 /* Same field as previous match... */
407 if (r > 0) {
409 /* Find the earliest of the OR matches */
411 if (!to ||
412 (direction == DIRECTION_DOWN && cp < tp) ||
413 (direction == DIRECTION_UP && cp > tp)) {
414 to = c;
415 tp = cp;
420 } else {
422 /* Previous term is finished, did anything match? */
423 if (!to)
424 return 0;
426 /* Find the last of the AND matches */
427 if (!o ||
428 (direction == DIRECTION_DOWN && tp > p) ||
429 (direction == DIRECTION_UP && tp < p)) {
430 o = to;
431 p = tp;
434 term_match = m;
436 if (r > 0) {
437 to = c;
438 tp = cp;
439 } else {
440 to = NULL;
441 tp = 0;
446 /* Last term is finished, did anything match? */
447 if (!to)
448 return 0;
450 if (!o ||
451 (direction == DIRECTION_DOWN && tp > p) ||
452 (direction == DIRECTION_UP && tp < p)) {
453 o = to;
454 p = tp;
457 if (!o)
458 return 0;
461 if (ret)
462 *ret = o;
464 if (offset)
465 *offset = p;
467 return 1;
470 static int next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
471 int r;
472 uint64_t cp;
473 Object *c;
475 assert(j);
476 assert(f);
477 assert(ret);
478 assert(offset);
480 c = *ret;
481 cp = *offset;
483 if (!j->matches) {
484 /* No matches is easy */
486 r = journal_file_next_entry(f, c, cp, direction, &c, &cp);
487 if (r <= 0)
488 return r;
490 if (ret)
491 *ret = c;
492 if (offset)
493 *offset = cp;
494 return 1;
497 /* So there are matches we have to adhere to, let's find the
498 * first entry that matches all of them */
500 for (;;) {
501 uint64_t np, n;
502 bool found, term_result = false;
503 Match *m, *term_match = NULL;
504 Object *npo = NULL;
506 n = journal_file_entry_n_items(c);
508 /* Make sure we don't match the entry we are starting
509 * from. */
510 found = cp != *offset;
512 np = 0;
513 LIST_FOREACH(matches, m, j->matches) {
514 uint64_t q, k;
515 Object *qo = NULL;
517 /* Let's check if this is the beginning of a
518 * new term, i.e. has a different field prefix
519 * as the preceeding match. */
520 if (!term_match) {
521 term_match = m;
522 term_result = false;
523 } else if (!same_field(term_match->data, term_match->size, m->data, m->size)) {
524 if (!term_result)
525 found = false;
527 term_match = m;
528 term_result = false;
531 for (k = 0; k < n; k++)
532 if (c->entry.items[k].hash == m->le_hash)
533 break;
535 if (k >= n) {
536 /* Hmm, didn't find any field that
537 * matched this rule, so ignore this
538 * match. Go on with next match */
539 continue;
542 term_result = true;
544 /* Hmm, so, this field matched, let's remember
545 * where we'd have to try next, in case the other
546 * matches are not OK */
548 r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, &qo, &q);
549 /* This pointer is invalidated if the window was
550 * remapped. May need to re-fetch it later */
551 c = NULL;
552 if (r < 0)
553 return r;
555 if (r > 0) {
557 if (direction == DIRECTION_DOWN) {
558 if (q > np) {
559 np = q;
560 npo = qo;
562 } else {
563 if (np == 0 || q < np) {
564 np = q;
565 npo = qo;
571 /* Check the last term */
572 if (term_match && !term_result)
573 found = false;
575 /* Did this entry match against all matches? */
576 if (found) {
577 if (ret) {
578 if (c == NULL) {
579 /* Re-fetch the entry */
580 r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
581 if (r < 0)
582 return r;
584 *ret = c;
586 if (offset)
587 *offset = cp;
588 return 1;
591 /* Did we find a subsequent entry? */
592 if (np == 0)
593 return 0;
595 /* Hmm, ok, this entry only matched partially, so
596 * let's try another one */
597 cp = np;
598 c = npo;
602 static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
603 Object *c;
604 uint64_t cp;
605 int compare_value, r;
607 assert(j);
608 assert(f);
610 if (f->current_offset > 0) {
611 cp = f->current_offset;
613 r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
614 if (r < 0)
615 return r;
617 r = next_with_matches(j, f, direction, &c, &cp);
618 if (r <= 0)
619 return r;
621 compare_value = 1;
622 } else {
623 r = find_location(j, f, direction, &c, &cp);
624 if (r <= 0)
625 return r;
627 compare_value = 0;
630 for (;;) {
631 bool found;
633 if (j->current_location.type == LOCATION_DISCRETE) {
634 int k;
636 k = compare_with_location(f, c, &j->current_location);
637 if (direction == DIRECTION_DOWN)
638 found = k >= compare_value;
639 else
640 found = k <= -compare_value;
641 } else
642 found = true;
644 if (found) {
645 if (ret)
646 *ret = c;
647 if (offset)
648 *offset = cp;
649 return 1;
652 r = next_with_matches(j, f, direction, &c, &cp);
653 if (r <= 0)
654 return r;
658 static int real_journal_next(sd_journal *j, direction_t direction) {
659 JournalFile *f, *new_current = NULL;
660 Iterator i;
661 int r;
662 uint64_t new_offset = 0;
663 Object *new_entry = NULL;
665 if (!j)
666 return -EINVAL;
668 HASHMAP_FOREACH(f, j->files, i) {
669 Object *o;
670 uint64_t p;
671 bool found;
673 r = next_beyond_location(j, f, direction, &o, &p);
674 if (r < 0) {
675 log_debug("Can't iterate through %s, ignoring: %s", f->path, strerror(-r));
676 continue;
677 } else if (r == 0)
678 continue;
680 if (!new_current)
681 found = true;
682 else {
683 int k;
685 k = compare_order(f, o, new_current, new_entry);
687 if (direction == DIRECTION_DOWN)
688 found = k < 0;
689 else
690 found = k > 0;
693 if (found) {
694 new_current = f;
695 new_entry = o;
696 new_offset = p;
700 if (!new_current)
701 return 0;
703 set_location(j, new_current, new_entry, new_offset);
705 return 1;
708 _public_ int sd_journal_next(sd_journal *j) {
709 return real_journal_next(j, DIRECTION_DOWN);
712 _public_ int sd_journal_previous(sd_journal *j) {
713 return real_journal_next(j, DIRECTION_UP);
716 static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
717 int c = 0, r;
719 if (!j)
720 return -EINVAL;
722 if (skip == 0) {
723 /* If this is not a discrete skip, then at least
724 * resolve the current location */
725 if (j->current_location.type != LOCATION_DISCRETE)
726 return real_journal_next(j, direction);
728 return 0;
731 do {
732 r = real_journal_next(j, direction);
733 if (r < 0)
734 return r;
736 if (r == 0)
737 return c;
739 skip--;
740 c++;
741 } while (skip > 0);
743 return c;
746 _public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
747 return real_journal_next_skip(j, DIRECTION_DOWN, skip);
750 _public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
751 return real_journal_next_skip(j, DIRECTION_UP, skip);
754 _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
755 Object *o;
756 int r;
757 char bid[33], sid[33];
759 if (!j)
760 return -EINVAL;
761 if (!cursor)
762 return -EINVAL;
764 if (!j->current_file || j->current_file->current_offset <= 0)
765 return -EADDRNOTAVAIL;
767 r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
768 if (r < 0)
769 return r;
771 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
772 sd_id128_to_string(o->entry.boot_id, bid);
774 if (asprintf(cursor,
775 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
776 sid, (unsigned long long) le64toh(o->entry.seqnum),
777 bid, (unsigned long long) le64toh(o->entry.monotonic),
778 (unsigned long long) le64toh(o->entry.realtime),
779 (unsigned long long) le64toh(o->entry.xor_hash),
780 path_get_file_name(j->current_file->path)) < 0)
781 return -ENOMEM;
783 return 1;
786 _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
787 char *w;
788 size_t l;
789 char *state;
790 unsigned long long seqnum, monotonic, realtime, xor_hash;
791 bool
792 seqnum_id_set = false,
793 seqnum_set = false,
794 boot_id_set = false,
795 monotonic_set = false,
796 realtime_set = false,
797 xor_hash_set = false;
798 sd_id128_t seqnum_id, boot_id;
800 if (!j)
801 return -EINVAL;
802 if (!cursor)
803 return -EINVAL;
805 FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
806 char *item;
807 int k = 0;
809 if (l < 2 || w[1] != '=')
810 return -EINVAL;
812 item = strndup(w, l);
813 if (!item)
814 return -ENOMEM;
816 switch (w[0]) {
818 case 's':
819 seqnum_id_set = true;
820 k = sd_id128_from_string(w+2, &seqnum_id);
821 break;
823 case 'i':
824 seqnum_set = true;
825 if (sscanf(w+2, "%llx", &seqnum) != 1)
826 k = -EINVAL;
827 break;
829 case 'b':
830 boot_id_set = true;
831 k = sd_id128_from_string(w+2, &boot_id);
832 break;
834 case 'm':
835 monotonic_set = true;
836 if (sscanf(w+2, "%llx", &monotonic) != 1)
837 k = -EINVAL;
838 break;
840 case 't':
841 realtime_set = true;
842 if (sscanf(w+2, "%llx", &realtime) != 1)
843 k = -EINVAL;
844 break;
846 case 'x':
847 xor_hash_set = true;
848 if (sscanf(w+2, "%llx", &xor_hash) != 1)
849 k = -EINVAL;
850 break;
853 free(item);
855 if (k < 0)
856 return k;
859 if ((!seqnum_set || !seqnum_id_set) &&
860 (!monotonic_set || !boot_id_set) &&
861 !realtime_set)
862 return -EINVAL;
864 reset_location(j);
866 j->current_location.type = LOCATION_DISCRETE;
868 if (realtime_set) {
869 j->current_location.realtime = (uint64_t) realtime;
870 j->current_location.realtime_set = true;
873 if (seqnum_set && seqnum_id_set) {
874 j->current_location.seqnum = (uint64_t) seqnum;
875 j->current_location.seqnum_id = seqnum_id;
876 j->current_location.seqnum_set = true;
879 if (monotonic_set && boot_id_set) {
880 j->current_location.monotonic = (uint64_t) monotonic;
881 j->current_location.boot_id = boot_id;
882 j->current_location.monotonic_set = true;
885 if (xor_hash_set) {
886 j->current_location.xor_hash = (uint64_t) xor_hash;
887 j->current_location.xor_hash_set = true;
890 return 0;
893 _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
894 if (!j)
895 return -EINVAL;
897 reset_location(j);
898 j->current_location.type = LOCATION_DISCRETE;
899 j->current_location.boot_id = boot_id;
900 j->current_location.monotonic = usec;
901 j->current_location.monotonic_set = true;
903 return 0;
906 _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
907 if (!j)
908 return -EINVAL;
910 reset_location(j);
911 j->current_location.type = LOCATION_DISCRETE;
912 j->current_location.realtime = usec;
913 j->current_location.realtime_set = true;
915 return 0;
918 _public_ int sd_journal_seek_head(sd_journal *j) {
919 if (!j)
920 return -EINVAL;
922 reset_location(j);
923 j->current_location.type = LOCATION_HEAD;
925 return 0;
928 _public_ int sd_journal_seek_tail(sd_journal *j) {
929 if (!j)
930 return -EINVAL;
932 reset_location(j);
933 j->current_location.type = LOCATION_TAIL;
935 return 0;
938 static int add_file(sd_journal *j, const char *prefix, const char *filename) {
939 char *path;
940 int r;
941 JournalFile *f;
943 assert(j);
944 assert(prefix);
945 assert(filename);
947 if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) &&
948 !(streq(filename, "system.journal") ||
949 (startswith(filename, "system@") && endswith(filename, ".journal"))))
950 return 0;
952 path = join(prefix, "/", filename, NULL);
953 if (!path)
954 return -ENOMEM;
956 if (hashmap_get(j->files, path)) {
957 free(path);
958 return 0;
961 if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
962 log_debug("Too many open journal files, not adding %s, ignoring.", path);
963 free(path);
964 return 0;
967 r = journal_file_open(path, O_RDONLY, 0, NULL, &f);
968 free(path);
970 if (r < 0) {
971 if (errno == ENOENT)
972 return 0;
974 return r;
977 /* journal_file_dump(f); */
979 r = hashmap_put(j->files, f->path, f);
980 if (r < 0) {
981 journal_file_close(f);
982 return r;
985 j->current_invalidate_counter ++;
987 log_debug("File %s got added.", f->path);
989 return 0;
992 static int remove_file(sd_journal *j, const char *prefix, const char *filename) {
993 char *path;
994 JournalFile *f;
996 assert(j);
997 assert(prefix);
998 assert(filename);
1000 path = join(prefix, "/", filename, NULL);
1001 if (!path)
1002 return -ENOMEM;
1004 f = hashmap_get(j->files, path);
1005 free(path);
1006 if (!f)
1007 return 0;
1009 hashmap_remove(j->files, f->path);
1010 journal_file_close(f);
1012 j->current_invalidate_counter ++;
1014 log_debug("File %s got removed.", f->path);
1015 return 0;
1018 static int add_directory(sd_journal *j, const char *prefix, const char *dirname) {
1019 char *path;
1020 int r;
1021 DIR *d;
1022 sd_id128_t id, mid;
1023 Directory *m;
1025 assert(j);
1026 assert(prefix);
1027 assert(dirname);
1029 if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
1030 (sd_id128_from_string(dirname, &id) < 0 ||
1031 sd_id128_get_machine(&mid) < 0 ||
1032 !sd_id128_equal(id, mid)))
1033 return 0;
1035 path = join(prefix, "/", dirname, NULL);
1036 if (!path)
1037 return -ENOMEM;
1039 d = opendir(path);
1040 if (!d) {
1041 log_debug("Failed to open %s: %m", path);
1042 free(path);
1044 if (errno == ENOENT)
1045 return 0;
1046 return -errno;
1049 m = hashmap_get(j->directories_by_path, path);
1050 if (!m) {
1051 m = new0(Directory, 1);
1052 if (!m) {
1053 closedir(d);
1054 free(path);
1055 return -ENOMEM;
1058 m->is_root = false;
1059 m->path = path;
1061 if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
1062 closedir(d);
1063 free(m->path);
1064 free(m);
1065 return -ENOMEM;
1068 j->current_invalidate_counter ++;
1070 log_debug("Directory %s got added.", m->path);
1072 } else if (m->is_root) {
1073 free (path);
1074 closedir(d);
1075 return 0;
1076 } else
1077 free(path);
1079 if (m->wd <= 0 && j->inotify_fd >= 0) {
1081 m->wd = inotify_add_watch(j->inotify_fd, m->path,
1082 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1083 IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|
1084 IN_DONT_FOLLOW|IN_ONLYDIR);
1086 if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
1087 inotify_rm_watch(j->inotify_fd, m->wd);
1090 for (;;) {
1091 struct dirent buf, *de;
1093 r = readdir_r(d, &buf, &de);
1094 if (r != 0 || !de)
1095 break;
1097 if (dirent_is_file_with_suffix(de, ".journal")) {
1098 r = add_file(j, m->path, de->d_name);
1099 if (r < 0)
1100 log_debug("Failed to add file %s/%s: %s", m->path, de->d_name, strerror(-r));
1104 closedir(d);
1106 return 0;
1109 static int add_root_directory(sd_journal *j, const char *p) {
1110 DIR *d;
1111 Directory *m;
1112 int r;
1114 assert(j);
1115 assert(p);
1117 if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) &&
1118 !path_startswith(p, "/run"))
1119 return -EINVAL;
1121 d = opendir(p);
1122 if (!d)
1123 return -errno;
1125 m = hashmap_get(j->directories_by_path, p);
1126 if (!m) {
1127 m = new0(Directory, 1);
1128 if (!m) {
1129 closedir(d);
1130 return -ENOMEM;
1133 m->is_root = true;
1134 m->path = strdup(p);
1135 if (!m->path) {
1136 closedir(d);
1137 free(m);
1138 return -ENOMEM;
1141 if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
1142 closedir(d);
1143 free(m->path);
1144 free(m);
1145 return -ENOMEM;
1148 j->current_invalidate_counter ++;
1150 log_debug("Root directory %s got added.", m->path);
1152 } else if (!m->is_root) {
1153 closedir(d);
1154 return 0;
1157 if (m->wd <= 0 && j->inotify_fd >= 0) {
1159 m->wd = inotify_add_watch(j->inotify_fd, m->path,
1160 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1161 IN_DONT_FOLLOW|IN_ONLYDIR);
1163 if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
1164 inotify_rm_watch(j->inotify_fd, m->wd);
1167 for (;;) {
1168 struct dirent buf, *de;
1169 sd_id128_t id;
1171 r = readdir_r(d, &buf, &de);
1172 if (r != 0 || !de)
1173 break;
1175 if (dirent_is_file_with_suffix(de, ".journal")) {
1176 r = add_file(j, m->path, de->d_name);
1177 if (r < 0)
1178 log_debug("Failed to add file %s/%s: %s", m->path, de->d_name, strerror(-r));
1180 } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
1181 sd_id128_from_string(de->d_name, &id) >= 0) {
1183 r = add_directory(j, m->path, de->d_name);
1184 if (r < 0)
1185 log_debug("Failed to add directory %s/%s: %s", m->path, de->d_name, strerror(-r));
1189 closedir(d);
1191 return 0;
1194 static int remove_directory(sd_journal *j, Directory *d) {
1195 assert(j);
1197 if (d->wd > 0) {
1198 hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd));
1200 if (j->inotify_fd >= 0)
1201 inotify_rm_watch(j->inotify_fd, d->wd);
1204 hashmap_remove(j->directories_by_path, d->path);
1206 if (d->is_root)
1207 log_debug("Root directory %s got removed.", d->path);
1208 else
1209 log_debug("Directory %s got removed.", d->path);
1211 free(d->path);
1212 free(d);
1214 return 0;
1217 static int add_search_paths(sd_journal *j) {
1219 const char search_paths[] =
1220 "/run/log/journal\0"
1221 "/var/log/journal\0";
1222 const char *p;
1224 assert(j);
1226 /* We ignore most errors here, since the idea is to only open
1227 * what's actually accessible, and ignore the rest. */
1229 NULSTR_FOREACH(p, search_paths)
1230 add_root_directory(j, p);
1232 return 0;
1235 static int allocate_inotify(sd_journal *j) {
1236 assert(j);
1238 if (j->inotify_fd < 0) {
1239 j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
1240 if (j->inotify_fd < 0)
1241 return -errno;
1244 if (!j->directories_by_wd) {
1245 j->directories_by_wd = hashmap_new(trivial_hash_func, trivial_compare_func);
1246 if (!j->directories_by_wd)
1247 return -ENOMEM;
1250 return 0;
1253 static sd_journal *journal_new(int flags) {
1254 sd_journal *j;
1256 j = new0(sd_journal, 1);
1257 if (!j)
1258 return NULL;
1260 j->inotify_fd = -1;
1261 j->flags = flags;
1263 j->files = hashmap_new(string_hash_func, string_compare_func);
1264 if (!j->files) {
1265 free(j);
1266 return NULL;
1269 j->directories_by_path = hashmap_new(string_hash_func, string_compare_func);
1270 if (!j->directories_by_path) {
1271 hashmap_free(j->files);
1272 free(j);
1273 return NULL;
1276 return j;
1279 _public_ int sd_journal_open(sd_journal **ret, int flags) {
1280 sd_journal *j;
1281 int r;
1283 if (!ret)
1284 return -EINVAL;
1286 if (flags & ~(SD_JOURNAL_LOCAL_ONLY|
1287 SD_JOURNAL_RUNTIME_ONLY|
1288 SD_JOURNAL_SYSTEM_ONLY))
1289 return -EINVAL;
1291 j = journal_new(flags);
1292 if (!j)
1293 return -ENOMEM;
1295 r = add_search_paths(j);
1296 if (r < 0)
1297 goto fail;
1299 *ret = j;
1300 return 0;
1302 fail:
1303 sd_journal_close(j);
1305 return r;
1308 _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) {
1309 sd_journal *j;
1310 int r;
1312 if (!ret)
1313 return -EINVAL;
1315 if (!path || !path_is_absolute(path))
1316 return -EINVAL;
1318 if (flags != 0)
1319 return -EINVAL;
1321 j = journal_new(flags);
1322 if (!j)
1323 return -ENOMEM;
1325 r = add_root_directory(j, path);
1326 if (r < 0)
1327 goto fail;
1329 *ret = j;
1330 return 0;
1332 fail:
1333 sd_journal_close(j);
1335 return r;
1338 _public_ void sd_journal_close(sd_journal *j) {
1339 Directory *d;
1340 JournalFile *f;
1342 if (!j)
1343 return;
1345 while ((f = hashmap_steal_first(j->files)))
1346 journal_file_close(f);
1348 hashmap_free(j->files);
1350 while ((d = hashmap_first(j->directories_by_path)))
1351 remove_directory(j, d);
1353 while ((d = hashmap_first(j->directories_by_wd)))
1354 remove_directory(j, d);
1356 hashmap_free(j->directories_by_path);
1357 hashmap_free(j->directories_by_wd);
1359 if (j->inotify_fd >= 0)
1360 close_nointr_nofail(j->inotify_fd);
1362 sd_journal_flush_matches(j);
1364 free(j);
1367 _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
1368 Object *o;
1369 JournalFile *f;
1370 int r;
1372 if (!j)
1373 return -EINVAL;
1374 if (!ret)
1375 return -EINVAL;
1377 f = j->current_file;
1378 if (!f)
1379 return -EADDRNOTAVAIL;
1381 if (f->current_offset <= 0)
1382 return -EADDRNOTAVAIL;
1384 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1385 if (r < 0)
1386 return r;
1388 *ret = le64toh(o->entry.realtime);
1389 return 0;
1392 _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
1393 Object *o;
1394 JournalFile *f;
1395 int r;
1396 sd_id128_t id;
1398 if (!j)
1399 return -EINVAL;
1401 f = j->current_file;
1402 if (!f)
1403 return -EADDRNOTAVAIL;
1405 if (f->current_offset <= 0)
1406 return -EADDRNOTAVAIL;
1408 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1409 if (r < 0)
1410 return r;
1412 if (ret_boot_id)
1413 *ret_boot_id = o->entry.boot_id;
1414 else {
1415 r = sd_id128_get_boot(&id);
1416 if (r < 0)
1417 return r;
1419 if (!sd_id128_equal(id, o->entry.boot_id))
1420 return -ESTALE;
1423 if (ret)
1424 *ret = le64toh(o->entry.monotonic);
1426 return 0;
1429 static bool field_is_valid(const char *field) {
1430 const char *p;
1432 assert(field);
1434 if (isempty(field))
1435 return false;
1437 if (startswith(field, "__"))
1438 return false;
1440 for (p = field; *p; p++) {
1442 if (*p == '_')
1443 continue;
1445 if (*p >= 'A' && *p <= 'Z')
1446 continue;
1448 if (*p >= '0' && *p <= '9')
1449 continue;
1451 return false;
1454 return true;
1457 _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
1458 JournalFile *f;
1459 uint64_t i, n;
1460 size_t field_length;
1461 int r;
1462 Object *o;
1464 if (!j)
1465 return -EINVAL;
1466 if (!field)
1467 return -EINVAL;
1468 if (!data)
1469 return -EINVAL;
1470 if (!size)
1471 return -EINVAL;
1473 if (!field_is_valid(field))
1474 return -EINVAL;
1476 f = j->current_file;
1477 if (!f)
1478 return -EADDRNOTAVAIL;
1480 if (f->current_offset <= 0)
1481 return -EADDRNOTAVAIL;
1483 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1484 if (r < 0)
1485 return r;
1487 field_length = strlen(field);
1489 n = journal_file_entry_n_items(o);
1490 for (i = 0; i < n; i++) {
1491 uint64_t p, l;
1492 le64_t le_hash;
1493 size_t t;
1495 p = le64toh(o->entry.items[i].object_offset);
1496 le_hash = o->entry.items[i].hash;
1497 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1498 if (r < 0)
1499 return r;
1501 if (le_hash != o->data.hash)
1502 return -EBADMSG;
1504 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1506 if (o->object.flags & OBJECT_COMPRESSED) {
1508 #ifdef HAVE_XZ
1509 if (uncompress_startswith(o->data.payload, l,
1510 &f->compress_buffer, &f->compress_buffer_size,
1511 field, field_length, '=')) {
1513 uint64_t rsize;
1515 if (!uncompress_blob(o->data.payload, l,
1516 &f->compress_buffer, &f->compress_buffer_size, &rsize))
1517 return -EBADMSG;
1519 *data = f->compress_buffer;
1520 *size = (size_t) rsize;
1522 return 0;
1524 #else
1525 return -EPROTONOSUPPORT;
1526 #endif
1528 } else if (l >= field_length+1 &&
1529 memcmp(o->data.payload, field, field_length) == 0 &&
1530 o->data.payload[field_length] == '=') {
1532 t = (size_t) l;
1534 if ((uint64_t) t != l)
1535 return -E2BIG;
1537 *data = o->data.payload;
1538 *size = t;
1540 return 0;
1543 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1544 if (r < 0)
1545 return r;
1548 return -ENOENT;
1551 _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
1552 JournalFile *f;
1553 uint64_t p, l, n;
1554 le64_t le_hash;
1555 int r;
1556 Object *o;
1557 size_t t;
1559 if (!j)
1560 return -EINVAL;
1561 if (!data)
1562 return -EINVAL;
1563 if (!size)
1564 return -EINVAL;
1566 f = j->current_file;
1567 if (!f)
1568 return -EADDRNOTAVAIL;
1570 if (f->current_offset <= 0)
1571 return -EADDRNOTAVAIL;
1573 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1574 if (r < 0)
1575 return r;
1577 n = journal_file_entry_n_items(o);
1578 if (j->current_field >= n)
1579 return 0;
1581 p = le64toh(o->entry.items[j->current_field].object_offset);
1582 le_hash = o->entry.items[j->current_field].hash;
1583 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1584 if (r < 0)
1585 return r;
1587 if (le_hash != o->data.hash)
1588 return -EBADMSG;
1590 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1591 t = (size_t) l;
1593 /* We can't read objects larger than 4G on a 32bit machine */
1594 if ((uint64_t) t != l)
1595 return -E2BIG;
1597 if (o->object.flags & OBJECT_COMPRESSED) {
1598 #ifdef HAVE_XZ
1599 uint64_t rsize;
1601 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
1602 return -EBADMSG;
1604 *data = f->compress_buffer;
1605 *size = (size_t) rsize;
1606 #else
1607 return -EPROTONOSUPPORT;
1608 #endif
1609 } else {
1610 *data = o->data.payload;
1611 *size = t;
1614 j->current_field ++;
1616 return 1;
1619 _public_ void sd_journal_restart_data(sd_journal *j) {
1620 if (!j)
1621 return;
1623 j->current_field = 0;
1626 _public_ int sd_journal_get_fd(sd_journal *j) {
1627 int r;
1629 if (!j)
1630 return -EINVAL;
1632 if (j->inotify_fd >= 0)
1633 return j->inotify_fd;
1635 r = allocate_inotify(j);
1636 if (r < 0)
1637 return r;
1639 /* Iterate through all dirs again, to add them to the
1640 * inotify */
1641 r = add_search_paths(j);
1642 if (r < 0)
1643 return r;
1645 return j->inotify_fd;
1648 static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
1649 Directory *d;
1650 int r;
1652 assert(j);
1653 assert(e);
1655 /* Is this a subdirectory we watch? */
1656 d = hashmap_get(j->directories_by_wd, INT_TO_PTR(e->wd));
1657 if (d) {
1658 sd_id128_t id;
1660 if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) {
1662 /* Event for a journal file */
1664 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1665 r = add_file(j, d->path, e->name);
1666 if (r < 0)
1667 log_debug("Failed to add file %s/%s: %s", d->path, e->name, strerror(-r));
1669 } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) {
1671 r = remove_file(j, d->path, e->name);
1672 if (r < 0)
1673 log_debug("Failed to remove file %s/%s: %s", d->path, e->name, strerror(-r));
1676 } else if (!d->is_root && e->len == 0) {
1678 /* Event for a subdirectory */
1680 if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) {
1681 r = remove_directory(j, d);
1682 if (r < 0)
1683 log_debug("Failed to remove directory %s: %s", d->path, strerror(-r));
1687 } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
1689 /* Event for root directory */
1691 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
1692 r = add_directory(j, d->path, e->name);
1693 if (r < 0)
1694 log_debug("Failed to add directory %s/%s: %s", d->path, e->name, strerror(-r));
1698 return;
1701 if (e->mask & IN_IGNORED)
1702 return;
1704 log_warning("Unknown inotify event.");
1707 static int determine_change(sd_journal *j) {
1708 bool b;
1710 assert(j);
1712 b = j->current_invalidate_counter != j->last_invalidate_counter;
1713 j->last_invalidate_counter = j->current_invalidate_counter;
1715 return b ? SD_JOURNAL_INVALIDATE : SD_JOURNAL_APPEND;
1718 _public_ int sd_journal_process(sd_journal *j) {
1719 uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX];
1720 bool got_something = false;
1722 if (!j)
1723 return -EINVAL;
1725 for (;;) {
1726 struct inotify_event *e;
1727 ssize_t l;
1729 l = read(j->inotify_fd, buffer, sizeof(buffer));
1730 if (l < 0) {
1731 if (errno == EAGAIN || errno == EINTR)
1732 return got_something ? determine_change(j) : SD_JOURNAL_NOP;
1734 return -errno;
1737 got_something = true;
1739 e = (struct inotify_event*) buffer;
1740 while (l > 0) {
1741 size_t step;
1743 process_inotify_event(j, e);
1745 step = sizeof(struct inotify_event) + e->len;
1746 assert(step <= (size_t) l);
1748 e = (struct inotify_event*) ((uint8_t*) e + step);
1749 l -= step;
1753 return determine_change(j);
1756 _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) {
1757 int r;
1759 assert(j);
1761 if (j->inotify_fd < 0) {
1763 /* This is the first invocation, hence create the
1764 * inotify watch */
1765 r = sd_journal_get_fd(j);
1766 if (r < 0)
1767 return r;
1769 /* The journal might have changed since the context
1770 * object was created and we weren't watching before,
1771 * hence don't wait for anything, and return
1772 * immediately. */
1773 return determine_change(j);
1776 do {
1777 r = fd_wait_for_event(j->inotify_fd, POLLIN, timeout_usec);
1778 } while (r == -EINTR);
1780 if (r < 0)
1781 return r;
1783 return sd_journal_process(j);
1786 _public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) {
1787 Iterator i;
1788 JournalFile *f;
1789 bool first = true;
1790 int r;
1792 if (!j)
1793 return -EINVAL;
1794 if (!from && !to)
1795 return -EINVAL;
1797 HASHMAP_FOREACH(f, j->files, i) {
1798 usec_t fr, t;
1800 r = journal_file_get_cutoff_realtime_usec(f, &fr, &t);
1801 if (r < 0)
1802 return r;
1803 if (r == 0)
1804 continue;
1806 if (first) {
1807 if (from)
1808 *from = fr;
1809 if (to)
1810 *to = t;
1811 first = false;
1812 } else {
1813 if (from)
1814 *from = MIN(fr, *from);
1815 if (to)
1816 *to = MIN(t, *to);
1820 return first ? 0 : 1;
1823 _public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) {
1824 Iterator i;
1825 JournalFile *f;
1826 bool first = true;
1827 int r;
1829 if (!j)
1830 return -EINVAL;
1831 if (!from && !to)
1832 return -EINVAL;
1834 HASHMAP_FOREACH(f, j->files, i) {
1835 usec_t fr, t;
1837 r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t);
1838 if (r < 0)
1839 return r;
1840 if (r == 0)
1841 continue;
1843 if (first) {
1844 if (from)
1845 *from = fr;
1846 if (to)
1847 *to = t;
1848 first = false;
1849 } else {
1850 if (from)
1851 *from = MIN(fr, *from);
1852 if (to)
1853 *to = MIN(t, *to);
1857 return first ? 0 : 1;
1861 /* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */
1862 /* if (!j) */
1863 /* return -EINVAL; */
1864 /* if (!field) */
1865 /* return -EINVAL; */
1867 /* return -ENOTSUP; */
1868 /* } */
1870 /* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */
1871 /* if (!j) */
1872 /* return -EINVAL; */
1873 /* if (!data) */
1874 /* return -EINVAL; */
1875 /* if (!l) */
1876 /* return -EINVAL; */
1878 /* return -ENOTSUP; */
1879 /* } */
1881 /* _public_ void sd_journal_restart_unique(sd_journal *j) { */
1882 /* if (!j) */
1883 /* return; */
1884 /* } */