1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
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/>.
26 #include <sys/inotify.h>
29 #include <linux/magic.h>
31 #include "sd-journal.h"
32 #include "journal-def.h"
33 #include "journal-file.h"
37 #include "path-util.h"
40 #include "journal-internal.h"
43 #include "replace-var.h"
46 #define JOURNAL_FILES_MAX 1024
48 #define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC)
50 #define REPLACE_VAR_MAX 256
52 #define DEFAULT_DATA_THRESHOLD (64*1024)
54 static void remove_file_real(sd_journal *j, JournalFile *f);
56 static bool journal_pid_changed(sd_journal *j) {
59 /* We don't support people creating a journal object and
60 * keeping it around over a fork(). Let's complain. */
62 return j->original_pid != getpid();
65 /* We return an error here only if we didn't manage to
66 memorize the real error. */
67 static int set_put_error(sd_journal *j, int r) {
73 k = set_ensure_allocated(&j->errors, NULL);
77 return set_put(j->errors, INT_TO_PTR(r));
80 static void detach_location(sd_journal *j) {
86 j->current_file = NULL;
89 ORDERED_HASHMAP_FOREACH(f, j->files, i)
90 f->current_offset = 0;
93 static void reset_location(sd_journal *j) {
97 zero(j->current_location);
100 static void init_location(Location *l, LocationType type, JournalFile *f, Object *o) {
102 assert(type == LOCATION_DISCRETE || type == LOCATION_SEEK);
104 assert(o->object.type == OBJECT_ENTRY);
107 l->seqnum = le64toh(o->entry.seqnum);
108 l->seqnum_id = f->header->seqnum_id;
109 l->realtime = le64toh(o->entry.realtime);
110 l->monotonic = le64toh(o->entry.monotonic);
111 l->boot_id = o->entry.boot_id;
112 l->xor_hash = le64toh(o->entry.xor_hash);
114 l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true;
117 static void set_location(sd_journal *j, LocationType type, JournalFile *f, Object *o,
118 direction_t direction, uint64_t offset) {
120 assert(type == LOCATION_DISCRETE || type == LOCATION_SEEK);
124 init_location(&j->current_location, type, f, o);
127 j->current_field = 0;
129 f->last_direction = direction;
130 f->current_offset = offset;
133 static int match_is_valid(const void *data, size_t size) {
141 if (startswith(data, "__"))
145 for (p = b; p < b + size; p++) {
153 if (*p >= 'A' && *p <= 'Z')
156 if (*p >= '0' && *p <= '9')
165 static bool same_field(const void *_a, size_t s, const void *_b, size_t t) {
166 const uint8_t *a = _a, *b = _b;
169 for (j = 0; j < s && j < t; j++) {
178 assert_not_reached("\"=\" not found");
181 static Match *match_new(Match *p, MatchType t) {
192 LIST_PREPEND(matches, p->matches, m);
198 static void match_free(Match *m) {
202 match_free(m->matches);
205 LIST_REMOVE(matches, m->parent->matches, m);
211 static void match_free_if_empty(Match *m) {
212 if (!m || m->matches)
218 _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
219 Match *l3, *l4, *add_here = NULL, *m;
222 assert_return(j, -EINVAL);
223 assert_return(!journal_pid_changed(j), -ECHILD);
224 assert_return(data, -EINVAL);
229 assert_return(match_is_valid(data, size), -EINVAL);
235 * level 4: concrete matches */
238 j->level0 = match_new(NULL, MATCH_AND_TERM);
244 j->level1 = match_new(j->level0, MATCH_OR_TERM);
250 j->level2 = match_new(j->level1, MATCH_AND_TERM);
255 assert(j->level0->type == MATCH_AND_TERM);
256 assert(j->level1->type == MATCH_OR_TERM);
257 assert(j->level2->type == MATCH_AND_TERM);
259 le_hash = htole64(hash64(data, size));
261 LIST_FOREACH(matches, l3, j->level2->matches) {
262 assert(l3->type == MATCH_OR_TERM);
264 LIST_FOREACH(matches, l4, l3->matches) {
265 assert(l4->type == MATCH_DISCRETE);
267 /* Exactly the same match already? Then ignore
269 if (l4->le_hash == le_hash &&
271 memcmp(l4->data, data, size) == 0)
274 /* Same field? Then let's add this to this OR term */
275 if (same_field(data, size, l4->data, l4->size)) {
286 add_here = match_new(j->level2, MATCH_OR_TERM);
291 m = match_new(add_here, MATCH_DISCRETE);
295 m->le_hash = le_hash;
297 m->data = memdup(data, size);
306 match_free_if_empty(add_here);
307 match_free_if_empty(j->level2);
308 match_free_if_empty(j->level1);
309 match_free_if_empty(j->level0);
314 _public_ int sd_journal_add_conjunction(sd_journal *j) {
315 assert_return(j, -EINVAL);
316 assert_return(!journal_pid_changed(j), -ECHILD);
324 if (!j->level1->matches)
333 _public_ int sd_journal_add_disjunction(sd_journal *j) {
334 assert_return(j, -EINVAL);
335 assert_return(!journal_pid_changed(j), -ECHILD);
346 if (!j->level2->matches)
353 static char *match_make_string(Match *m) {
356 bool enclose = false;
359 return strdup("none");
361 if (m->type == MATCH_DISCRETE)
362 return strndup(m->data, m->size);
365 LIST_FOREACH(matches, i, m->matches) {
368 t = match_make_string(i);
375 k = strjoin(p, m->type == MATCH_OR_TERM ? " OR " : " AND ", t, NULL);
390 r = strjoin("(", p, ")", NULL);
398 char *journal_make_match_string(sd_journal *j) {
401 return match_make_string(j->level0);
404 _public_ void sd_journal_flush_matches(sd_journal *j) {
409 match_free(j->level0);
411 j->level0 = j->level1 = j->level2 = NULL;
416 static int compare_entry_order(JournalFile *af, Object *_ao,
417 JournalFile *bf, uint64_t bp) {
427 /* The mmap cache might invalidate the object from the first
428 * file if we look at the one from the second file. Hence
429 * temporarily copy the header of the first one, and look at
431 ao = alloca(offsetof(EntryObject, items));
432 memcpy(ao, _ao, offsetof(EntryObject, items));
434 r = journal_file_move_to_object(bf, OBJECT_ENTRY, bp, &bo);
436 return strcmp(af->path, bf->path);
438 /* We operate on two different files here, hence we can access
439 * two objects at the same time, which we normally can't.
441 * If contents and timestamps match, these entries are
442 * identical, even if the seqnum does not match */
444 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
445 ao->entry.monotonic == bo->entry.monotonic &&
446 ao->entry.realtime == bo->entry.realtime &&
447 ao->entry.xor_hash == bo->entry.xor_hash)
450 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
452 /* If this is from the same seqnum source, compare
454 a = le64toh(ao->entry.seqnum);
455 b = le64toh(bo->entry.seqnum);
462 /* Wow! This is weird, different data but the same
463 * seqnums? Something is borked, but let's make the
464 * best of it and compare by time. */
467 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
469 /* If the boot id matches, compare monotonic time */
470 a = le64toh(ao->entry.monotonic);
471 b = le64toh(bo->entry.monotonic);
479 /* Otherwise, compare UTC time */
480 a = le64toh(ao->entry.realtime);
481 b = le64toh(bo->entry.realtime);
488 /* Finally, compare by contents */
489 a = le64toh(ao->entry.xor_hash);
490 b = le64toh(bo->entry.xor_hash);
500 _pure_ static int compare_with_location(JournalFile *af, Object *ao, Location *l) {
506 assert(l->type == LOCATION_DISCRETE || l->type == LOCATION_SEEK);
508 if (l->monotonic_set &&
509 sd_id128_equal(ao->entry.boot_id, l->boot_id) &&
511 le64toh(ao->entry.realtime) == l->realtime &&
513 le64toh(ao->entry.xor_hash) == l->xor_hash)
517 sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) {
519 a = le64toh(ao->entry.seqnum);
527 if (l->monotonic_set &&
528 sd_id128_equal(ao->entry.boot_id, l->boot_id)) {
530 a = le64toh(ao->entry.monotonic);
532 if (a < l->monotonic)
534 if (a > l->monotonic)
538 if (l->realtime_set) {
540 a = le64toh(ao->entry.realtime);
548 if (l->xor_hash_set) {
549 a = le64toh(ao->entry.xor_hash);
560 static int next_for_match(
564 uint64_t after_offset,
565 direction_t direction,
577 if (m->type == MATCH_DISCRETE) {
580 r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp);
584 return journal_file_move_to_entry_by_offset_for_data(f, dp, after_offset, direction, ret, offset);
586 } else if (m->type == MATCH_OR_TERM) {
589 /* Find the earliest match beyond after_offset */
591 LIST_FOREACH(matches, i, m->matches) {
594 r = next_for_match(j, i, f, after_offset, direction, NULL, &cp);
598 if (np == 0 || (direction == DIRECTION_DOWN ? cp < np : cp > np))
606 } else if (m->type == MATCH_AND_TERM) {
607 Match *i, *last_moved;
609 /* Always jump to the next matching entry and repeat
610 * this until we find an offset that matches for all
616 r = next_for_match(j, m->matches, f, after_offset, direction, NULL, &np);
620 assert(direction == DIRECTION_DOWN ? np >= after_offset : np <= after_offset);
621 last_moved = m->matches;
623 LIST_LOOP_BUT_ONE(matches, i, m->matches, last_moved) {
626 r = next_for_match(j, i, f, np, direction, NULL, &cp);
630 assert(direction == DIRECTION_DOWN ? cp >= np : cp <= np);
631 if (direction == DIRECTION_DOWN ? cp > np : cp < np) {
640 r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n);
652 static int find_location_for_match(
656 direction_t direction,
666 if (m->type == MATCH_DISCRETE) {
669 r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp);
673 /* FIXME: missing: find by monotonic */
675 if (j->current_location.type == LOCATION_HEAD)
676 return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, ret, offset);
677 if (j->current_location.type == LOCATION_TAIL)
678 return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, ret, offset);
679 if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
680 return journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, ret, offset);
681 if (j->current_location.monotonic_set) {
682 r = journal_file_move_to_entry_by_monotonic_for_data(f, dp, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset);
686 if (j->current_location.realtime_set)
687 return journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, ret, offset);
689 return journal_file_next_entry_for_data(f, NULL, 0, dp, direction, ret, offset);
691 } else if (m->type == MATCH_OR_TERM) {
696 /* Find the earliest match */
698 LIST_FOREACH(matches, i, m->matches) {
701 r = find_location_for_match(j, i, f, direction, NULL, &cp);
705 if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp))
713 r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n);
728 assert(m->type == MATCH_AND_TERM);
730 /* First jump to the last match, and then find the
731 * next one where all matches match */
736 LIST_FOREACH(matches, i, m->matches) {
739 r = find_location_for_match(j, i, f, direction, NULL, &cp);
743 if (np == 0 || (direction == DIRECTION_DOWN ? cp > np : cp < np))
747 return next_for_match(j, m, f, np, direction, ret, offset);
751 static int find_location_with_matches(
754 direction_t direction,
766 /* No matches is simple */
768 if (j->current_location.type == LOCATION_HEAD)
769 return journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, ret, offset);
770 if (j->current_location.type == LOCATION_TAIL)
771 return journal_file_next_entry(f, NULL, 0, DIRECTION_UP, ret, offset);
772 if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
773 return journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, ret, offset);
774 if (j->current_location.monotonic_set) {
775 r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset);
779 if (j->current_location.realtime_set)
780 return journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, ret, offset);
782 return journal_file_next_entry(f, NULL, 0, direction, ret, offset);
784 return find_location_for_match(j, j->level0, f, direction, ret, offset);
787 static int next_with_matches(
790 direction_t direction,
805 /* No matches is easy. We simple advance the file
808 return journal_file_next_entry(f, c, cp, direction, ret, offset);
810 /* If we have a match then we look for the next matching entry
811 * with an offset at least one step larger */
812 return next_for_match(j, j->level0, f, direction == DIRECTION_DOWN ? cp+1 : cp-1, direction, ret, offset);
815 static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
823 if (f->last_direction == direction && f->current_offset > 0) {
824 cp = f->current_offset;
826 r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
830 r = next_with_matches(j, f, direction, &c, &cp);
834 r = find_location_with_matches(j, f, direction, &c, &cp);
839 /* OK, we found the spot, now let's advance until an entry
840 * that is actually different from what we were previously
841 * looking at. This is necessary to handle entries which exist
842 * in two (or more) journal files, and which shall all be
843 * suppressed but one. */
848 if (j->current_location.type == LOCATION_DISCRETE) {
851 k = compare_with_location(f, c, &j->current_location);
853 found = direction == DIRECTION_DOWN ? k > 0 : k < 0;
865 r = next_with_matches(j, f, direction, &c, &cp);
871 static int real_journal_next(sd_journal *j, direction_t direction) {
872 JournalFile *f, *new_file = NULL;
873 uint64_t new_offset = 0;
879 assert_return(j, -EINVAL);
880 assert_return(!journal_pid_changed(j), -ECHILD);
882 ORDERED_HASHMAP_FOREACH(f, j->files, i) {
885 r = next_beyond_location(j, f, direction, &o, &p);
887 log_debug("Can't iterate through %s, ignoring: %s", f->path, strerror(-r));
888 remove_file_real(j, f);
898 k = compare_entry_order(f, o, new_file, new_offset);
900 found = direction == DIRECTION_DOWN ? k < 0 : k > 0;
912 r = journal_file_move_to_object(new_file, OBJECT_ENTRY, new_offset, &o);
916 set_location(j, LOCATION_DISCRETE, new_file, o, direction, new_offset);
921 _public_ int sd_journal_next(sd_journal *j) {
922 return real_journal_next(j, DIRECTION_DOWN);
925 _public_ int sd_journal_previous(sd_journal *j) {
926 return real_journal_next(j, DIRECTION_UP);
929 static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
932 assert_return(j, -EINVAL);
933 assert_return(!journal_pid_changed(j), -ECHILD);
936 /* If this is not a discrete skip, then at least
937 * resolve the current location */
938 if (j->current_location.type != LOCATION_DISCRETE)
939 return real_journal_next(j, direction);
945 r = real_journal_next(j, direction);
959 _public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
960 return real_journal_next_skip(j, DIRECTION_DOWN, skip);
963 _public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
964 return real_journal_next_skip(j, DIRECTION_UP, skip);
967 _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
970 char bid[33], sid[33];
972 assert_return(j, -EINVAL);
973 assert_return(!journal_pid_changed(j), -ECHILD);
974 assert_return(cursor, -EINVAL);
976 if (!j->current_file || j->current_file->current_offset <= 0)
977 return -EADDRNOTAVAIL;
979 r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
983 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
984 sd_id128_to_string(o->entry.boot_id, bid);
987 "s=%s;i=%"PRIx64";b=%s;m=%"PRIx64";t=%"PRIx64";x=%"PRIx64,
988 sid, le64toh(o->entry.seqnum),
989 bid, le64toh(o->entry.monotonic),
990 le64toh(o->entry.realtime),
991 le64toh(o->entry.xor_hash)) < 0)
997 _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
998 const char *word, *state;
1000 unsigned long long seqnum, monotonic, realtime, xor_hash;
1002 seqnum_id_set = false,
1004 boot_id_set = false,
1005 monotonic_set = false,
1006 realtime_set = false,
1007 xor_hash_set = false;
1008 sd_id128_t seqnum_id, boot_id;
1010 assert_return(j, -EINVAL);
1011 assert_return(!journal_pid_changed(j), -ECHILD);
1012 assert_return(!isempty(cursor), -EINVAL);
1014 FOREACH_WORD_SEPARATOR(word, l, cursor, ";", state) {
1018 if (l < 2 || word[1] != '=')
1021 item = strndup(word, l);
1028 seqnum_id_set = true;
1029 k = sd_id128_from_string(item+2, &seqnum_id);
1034 if (sscanf(item+2, "%llx", &seqnum) != 1)
1040 k = sd_id128_from_string(item+2, &boot_id);
1044 monotonic_set = true;
1045 if (sscanf(item+2, "%llx", &monotonic) != 1)
1050 realtime_set = true;
1051 if (sscanf(item+2, "%llx", &realtime) != 1)
1056 xor_hash_set = true;
1057 if (sscanf(item+2, "%llx", &xor_hash) != 1)
1068 if ((!seqnum_set || !seqnum_id_set) &&
1069 (!monotonic_set || !boot_id_set) &&
1075 j->current_location.type = LOCATION_SEEK;
1078 j->current_location.realtime = (uint64_t) realtime;
1079 j->current_location.realtime_set = true;
1082 if (seqnum_set && seqnum_id_set) {
1083 j->current_location.seqnum = (uint64_t) seqnum;
1084 j->current_location.seqnum_id = seqnum_id;
1085 j->current_location.seqnum_set = true;
1088 if (monotonic_set && boot_id_set) {
1089 j->current_location.monotonic = (uint64_t) monotonic;
1090 j->current_location.boot_id = boot_id;
1091 j->current_location.monotonic_set = true;
1095 j->current_location.xor_hash = (uint64_t) xor_hash;
1096 j->current_location.xor_hash_set = true;
1102 _public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) {
1104 const char *word, *state;
1108 assert_return(j, -EINVAL);
1109 assert_return(!journal_pid_changed(j), -ECHILD);
1110 assert_return(!isempty(cursor), -EINVAL);
1112 if (!j->current_file || j->current_file->current_offset <= 0)
1113 return -EADDRNOTAVAIL;
1115 r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
1119 FOREACH_WORD_SEPARATOR(word, l, cursor, ";", state) {
1120 _cleanup_free_ char *item = NULL;
1122 unsigned long long ll;
1125 if (l < 2 || word[1] != '=')
1128 item = strndup(word, l);
1135 k = sd_id128_from_string(item+2, &id);
1138 if (!sd_id128_equal(id, j->current_file->header->seqnum_id))
1143 if (sscanf(item+2, "%llx", &ll) != 1)
1145 if (ll != le64toh(o->entry.seqnum))
1150 k = sd_id128_from_string(item+2, &id);
1153 if (!sd_id128_equal(id, o->entry.boot_id))
1158 if (sscanf(item+2, "%llx", &ll) != 1)
1160 if (ll != le64toh(o->entry.monotonic))
1165 if (sscanf(item+2, "%llx", &ll) != 1)
1167 if (ll != le64toh(o->entry.realtime))
1172 if (sscanf(item+2, "%llx", &ll) != 1)
1174 if (ll != le64toh(o->entry.xor_hash))
1184 _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
1185 assert_return(j, -EINVAL);
1186 assert_return(!journal_pid_changed(j), -ECHILD);
1189 j->current_location.type = LOCATION_SEEK;
1190 j->current_location.boot_id = boot_id;
1191 j->current_location.monotonic = usec;
1192 j->current_location.monotonic_set = true;
1197 _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
1198 assert_return(j, -EINVAL);
1199 assert_return(!journal_pid_changed(j), -ECHILD);
1202 j->current_location.type = LOCATION_SEEK;
1203 j->current_location.realtime = usec;
1204 j->current_location.realtime_set = true;
1209 _public_ int sd_journal_seek_head(sd_journal *j) {
1210 assert_return(j, -EINVAL);
1211 assert_return(!journal_pid_changed(j), -ECHILD);
1214 j->current_location.type = LOCATION_HEAD;
1219 _public_ int sd_journal_seek_tail(sd_journal *j) {
1220 assert_return(j, -EINVAL);
1221 assert_return(!journal_pid_changed(j), -ECHILD);
1224 j->current_location.type = LOCATION_TAIL;
1229 static void check_network(sd_journal *j, int fd) {
1237 if (fstatfs(fd, &sfs) < 0)
1241 F_TYPE_EQUAL(sfs.f_type, CIFS_MAGIC_NUMBER) ||
1242 F_TYPE_EQUAL(sfs.f_type, CODA_SUPER_MAGIC) ||
1243 F_TYPE_EQUAL(sfs.f_type, NCP_SUPER_MAGIC) ||
1244 F_TYPE_EQUAL(sfs.f_type, NFS_SUPER_MAGIC) ||
1245 F_TYPE_EQUAL(sfs.f_type, SMB_SUPER_MAGIC);
1248 static bool file_has_type_prefix(const char *prefix, const char *filename) {
1249 const char *full, *tilded, *atted;
1251 full = strappenda(prefix, ".journal");
1252 tilded = strappenda(full, "~");
1253 atted = strappenda(prefix, "@");
1255 return streq(filename, full) ||
1256 streq(filename, tilded) ||
1257 startswith(filename, atted);
1260 static bool file_type_wanted(int flags, const char *filename) {
1261 if (!endswith(filename, ".journal") && !endswith(filename, ".journal~"))
1264 /* no flags set → every type is OK */
1265 if (!(flags & (SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER)))
1268 if (flags & SD_JOURNAL_SYSTEM && file_has_type_prefix("system", filename))
1271 if (flags & SD_JOURNAL_CURRENT_USER) {
1272 char prefix[5 + DECIMAL_STR_MAX(uid_t) + 1];
1274 assert_se(snprintf(prefix, sizeof(prefix), "user-"UID_FMT, getuid())
1275 < (int) sizeof(prefix));
1277 if (file_has_type_prefix(prefix, filename))
1284 static int add_any_file(sd_journal *j, const char *path) {
1285 JournalFile *f = NULL;
1291 if (ordered_hashmap_get(j->files, path))
1294 if (ordered_hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
1295 log_warning("Too many open journal files, not adding %s.", path);
1296 return set_put_error(j, -ETOOMANYREFS);
1299 r = journal_file_open(path, O_RDONLY, 0, false, false, NULL, j->mmap, NULL, &f);
1303 /* journal_file_dump(f); */
1305 r = ordered_hashmap_put(j->files, f->path, f);
1307 journal_file_close(f);
1311 log_debug("File %s added.", f->path);
1313 check_network(j, f->fd);
1315 j->current_invalidate_counter ++;
1320 static int add_file(sd_journal *j, const char *prefix, const char *filename) {
1321 _cleanup_free_ char *path = NULL;
1328 if (j->no_new_files ||
1329 !file_type_wanted(j->flags, filename))
1332 path = strjoin(prefix, "/", filename, NULL);
1336 r = add_any_file(j, path);
1342 static int remove_file(sd_journal *j, const char *prefix, const char *filename) {
1343 _cleanup_free_ char *path;
1350 path = strjoin(prefix, "/", filename, NULL);
1354 f = ordered_hashmap_get(j->files, path);
1358 remove_file_real(j, f);
1362 static void remove_file_real(sd_journal *j, JournalFile *f) {
1366 ordered_hashmap_remove(j->files, f->path);
1368 log_debug("File %s removed.", f->path);
1370 if (j->current_file == f) {
1371 j->current_file = NULL;
1372 j->current_field = 0;
1375 if (j->unique_file == f) {
1376 /* Jump to the next unique_file or NULL if that one was last */
1377 j->unique_file = ordered_hashmap_next(j->files, j->unique_file->path);
1378 j->unique_offset = 0;
1379 if (!j->unique_file)
1380 j->unique_file_lost = true;
1383 journal_file_close(f);
1385 j->current_invalidate_counter ++;
1388 static int add_directory(sd_journal *j, const char *prefix, const char *dirname) {
1389 _cleanup_free_ char *path = NULL;
1391 _cleanup_closedir_ DIR *d = NULL;
1399 log_debug("Considering %s/%s.", prefix, dirname);
1401 if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
1402 (sd_id128_from_string(dirname, &id) < 0 ||
1403 sd_id128_get_machine(&mid) < 0 ||
1404 !(sd_id128_equal(id, mid) || path_startswith(prefix, "/run"))))
1407 path = strjoin(prefix, "/", dirname, NULL);
1413 log_debug("Failed to open %s: %m", path);
1414 if (errno == ENOENT)
1419 m = hashmap_get(j->directories_by_path, path);
1421 m = new0(Directory, 1);
1428 if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
1433 path = NULL; /* avoid freeing in cleanup */
1434 j->current_invalidate_counter ++;
1436 log_debug("Directory %s added.", m->path);
1438 } else if (m->is_root)
1441 if (m->wd <= 0 && j->inotify_fd >= 0) {
1443 m->wd = inotify_add_watch(j->inotify_fd, m->path,
1444 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1445 IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|IN_MOVED_FROM|
1448 if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
1449 inotify_rm_watch(j->inotify_fd, m->wd);
1457 if (!de && errno != 0) {
1459 log_debug("Failed to read directory %s: %m", m->path);
1465 if (dirent_is_file_with_suffix(de, ".journal") ||
1466 dirent_is_file_with_suffix(de, ".journal~")) {
1467 r = add_file(j, m->path, de->d_name);
1469 log_debug("Failed to add file %s/%s: %s",
1470 m->path, de->d_name, strerror(-r));
1471 r = set_put_error(j, r);
1478 check_network(j, dirfd(d));
1483 static int add_root_directory(sd_journal *j, const char *p) {
1484 _cleanup_closedir_ DIR *d = NULL;
1491 if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) &&
1492 !path_startswith(p, "/run"))
1496 p = strappenda(j->prefix, p);
1502 m = hashmap_get(j->directories_by_path, p);
1504 m = new0(Directory, 1);
1509 m->path = strdup(p);
1515 if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
1521 j->current_invalidate_counter ++;
1523 log_debug("Root directory %s added.", m->path);
1525 } else if (!m->is_root)
1528 if (m->wd <= 0 && j->inotify_fd >= 0) {
1530 m->wd = inotify_add_watch(j->inotify_fd, m->path,
1531 IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
1534 if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
1535 inotify_rm_watch(j->inotify_fd, m->wd);
1538 if (j->no_new_files)
1547 if (!de && errno != 0) {
1549 log_debug("Failed to read directory %s: %m", m->path);
1555 if (dirent_is_file_with_suffix(de, ".journal") ||
1556 dirent_is_file_with_suffix(de, ".journal~")) {
1557 r = add_file(j, m->path, de->d_name);
1559 log_debug("Failed to add file %s/%s: %s",
1560 m->path, de->d_name, strerror(-r));
1561 r = set_put_error(j, r);
1565 } else if ((de->d_type == DT_DIR || de->d_type == DT_LNK || de->d_type == DT_UNKNOWN) &&
1566 sd_id128_from_string(de->d_name, &id) >= 0) {
1568 r = add_directory(j, m->path, de->d_name);
1570 log_debug("Failed to add directory %s/%s: %s", m->path, de->d_name, strerror(-r));
1574 check_network(j, dirfd(d));
1579 static int remove_directory(sd_journal *j, Directory *d) {
1583 hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd));
1585 if (j->inotify_fd >= 0)
1586 inotify_rm_watch(j->inotify_fd, d->wd);
1589 hashmap_remove(j->directories_by_path, d->path);
1592 log_debug("Root directory %s removed.", d->path);
1594 log_debug("Directory %s removed.", d->path);
1602 static int add_search_paths(sd_journal *j) {
1604 const char search_paths[] =
1605 "/run/log/journal\0"
1606 "/var/log/journal\0";
1611 /* We ignore most errors here, since the idea is to only open
1612 * what's actually accessible, and ignore the rest. */
1614 NULSTR_FOREACH(p, search_paths) {
1615 r = add_root_directory(j, p);
1616 if (r < 0 && r != -ENOENT) {
1617 r = set_put_error(j, r);
1626 static int add_current_paths(sd_journal *j) {
1631 assert(j->no_new_files);
1633 /* Simply adds all directories for files we have open as
1634 * "root" directories. We don't expect errors here, so we
1635 * treat them as fatal. */
1637 ORDERED_HASHMAP_FOREACH(f, j->files, i) {
1638 _cleanup_free_ char *dir;
1641 dir = dirname_malloc(f->path);
1645 r = add_root_directory(j, dir);
1647 set_put_error(j, r);
1656 static int allocate_inotify(sd_journal *j) {
1659 if (j->inotify_fd < 0) {
1660 j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
1661 if (j->inotify_fd < 0)
1665 if (!j->directories_by_wd) {
1666 j->directories_by_wd = hashmap_new(NULL);
1667 if (!j->directories_by_wd)
1674 static sd_journal *journal_new(int flags, const char *path) {
1677 j = new0(sd_journal, 1);
1681 j->original_pid = getpid();
1684 j->data_threshold = DEFAULT_DATA_THRESHOLD;
1687 j->path = strdup(path);
1692 j->files = ordered_hashmap_new(&string_hash_ops);
1693 j->directories_by_path = hashmap_new(&string_hash_ops);
1694 j->mmap = mmap_cache_new();
1695 if (!j->files || !j->directories_by_path || !j->mmap)
1701 sd_journal_close(j);
1705 _public_ int sd_journal_open(sd_journal **ret, int flags) {
1709 assert_return(ret, -EINVAL);
1710 assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_RUNTIME_ONLY|SD_JOURNAL_SYSTEM|SD_JOURNAL_CURRENT_USER)) == 0, -EINVAL);
1712 j = journal_new(flags, NULL);
1716 r = add_search_paths(j);
1724 sd_journal_close(j);
1729 _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) {
1730 _cleanup_free_ char *root = NULL, *class = NULL;
1735 assert_return(machine, -EINVAL);
1736 assert_return(ret, -EINVAL);
1737 assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM)) == 0, -EINVAL);
1738 assert_return(machine_name_is_valid(machine), -EINVAL);
1740 p = strappenda("/run/systemd/machines/", machine);
1741 r = parse_env_file(p, NEWLINE, "ROOT", &root, "CLASS", &class, NULL);
1749 if (!streq_ptr(class, "container"))
1752 j = journal_new(flags, NULL);
1759 r = add_search_paths(j);
1767 sd_journal_close(j);
1771 _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) {
1775 assert_return(ret, -EINVAL);
1776 assert_return(path, -EINVAL);
1777 assert_return(flags == 0, -EINVAL);
1779 j = journal_new(flags, path);
1783 r = add_root_directory(j, path);
1785 set_put_error(j, r);
1793 sd_journal_close(j);
1798 _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int flags) {
1803 assert_return(ret, -EINVAL);
1804 assert_return(flags == 0, -EINVAL);
1806 j = journal_new(flags, NULL);
1810 STRV_FOREACH(path, paths) {
1811 r = add_any_file(j, *path);
1813 log_error("Failed to open %s: %s", *path, strerror(-r));
1818 j->no_new_files = true;
1824 sd_journal_close(j);
1829 _public_ void sd_journal_close(sd_journal *j) {
1836 sd_journal_flush_matches(j);
1838 while ((f = ordered_hashmap_steal_first(j->files)))
1839 journal_file_close(f);
1841 ordered_hashmap_free(j->files);
1843 while ((d = hashmap_first(j->directories_by_path)))
1844 remove_directory(j, d);
1846 while ((d = hashmap_first(j->directories_by_wd)))
1847 remove_directory(j, d);
1849 hashmap_free(j->directories_by_path);
1850 hashmap_free(j->directories_by_wd);
1852 safe_close(j->inotify_fd);
1855 log_debug("mmap cache statistics: %u hit, %u miss", mmap_cache_get_hit(j->mmap), mmap_cache_get_missed(j->mmap));
1856 mmap_cache_unref(j->mmap);
1861 free(j->unique_field);
1862 set_free(j->errors);
1866 _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
1871 assert_return(j, -EINVAL);
1872 assert_return(!journal_pid_changed(j), -ECHILD);
1873 assert_return(ret, -EINVAL);
1875 f = j->current_file;
1877 return -EADDRNOTAVAIL;
1879 if (f->current_offset <= 0)
1880 return -EADDRNOTAVAIL;
1882 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1886 *ret = le64toh(o->entry.realtime);
1890 _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
1896 assert_return(j, -EINVAL);
1897 assert_return(!journal_pid_changed(j), -ECHILD);
1899 f = j->current_file;
1901 return -EADDRNOTAVAIL;
1903 if (f->current_offset <= 0)
1904 return -EADDRNOTAVAIL;
1906 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1911 *ret_boot_id = o->entry.boot_id;
1913 r = sd_id128_get_boot(&id);
1917 if (!sd_id128_equal(id, o->entry.boot_id))
1922 *ret = le64toh(o->entry.monotonic);
1927 static bool field_is_valid(const char *field) {
1935 if (startswith(field, "__"))
1938 for (p = field; *p; p++) {
1943 if (*p >= 'A' && *p <= 'Z')
1946 if (*p >= '0' && *p <= '9')
1955 _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
1958 size_t field_length;
1962 assert_return(j, -EINVAL);
1963 assert_return(!journal_pid_changed(j), -ECHILD);
1964 assert_return(field, -EINVAL);
1965 assert_return(data, -EINVAL);
1966 assert_return(size, -EINVAL);
1967 assert_return(field_is_valid(field), -EINVAL);
1969 f = j->current_file;
1971 return -EADDRNOTAVAIL;
1973 if (f->current_offset <= 0)
1974 return -EADDRNOTAVAIL;
1976 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1980 field_length = strlen(field);
1982 n = journal_file_entry_n_items(o);
1983 for (i = 0; i < n; i++) {
1989 p = le64toh(o->entry.items[i].object_offset);
1990 le_hash = o->entry.items[i].hash;
1991 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1995 if (le_hash != o->data.hash)
1998 l = le64toh(o->object.size) - offsetof(Object, data.payload);
2000 compression = o->object.flags & OBJECT_COMPRESSION_MASK;
2002 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
2003 if (decompress_startswith(compression,
2005 &f->compress_buffer, &f->compress_buffer_size,
2006 field, field_length, '=')) {
2010 r = decompress_blob(compression,
2012 &f->compress_buffer, &f->compress_buffer_size, &rsize,
2017 *data = f->compress_buffer;
2018 *size = (size_t) rsize;
2023 return -EPROTONOSUPPORT;
2025 } else if (l >= field_length+1 &&
2026 memcmp(o->data.payload, field, field_length) == 0 &&
2027 o->data.payload[field_length] == '=') {
2031 if ((uint64_t) t != l)
2034 *data = o->data.payload;
2040 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
2048 static int return_data(sd_journal *j, JournalFile *f, Object *o, const void **data, size_t *size) {
2053 l = le64toh(o->object.size) - offsetof(Object, data.payload);
2056 /* We can't read objects larger than 4G on a 32bit machine */
2057 if ((uint64_t) t != l)
2060 compression = o->object.flags & OBJECT_COMPRESSION_MASK;
2062 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
2066 r = decompress_blob(compression,
2067 o->data.payload, l, &f->compress_buffer,
2068 &f->compress_buffer_size, &rsize, j->data_threshold);
2072 *data = f->compress_buffer;
2073 *size = (size_t) rsize;
2075 return -EPROTONOSUPPORT;
2078 *data = o->data.payload;
2085 _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
2092 assert_return(j, -EINVAL);
2093 assert_return(!journal_pid_changed(j), -ECHILD);
2094 assert_return(data, -EINVAL);
2095 assert_return(size, -EINVAL);
2097 f = j->current_file;
2099 return -EADDRNOTAVAIL;
2101 if (f->current_offset <= 0)
2102 return -EADDRNOTAVAIL;
2104 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
2108 n = journal_file_entry_n_items(o);
2109 if (j->current_field >= n)
2112 p = le64toh(o->entry.items[j->current_field].object_offset);
2113 le_hash = o->entry.items[j->current_field].hash;
2114 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
2118 if (le_hash != o->data.hash)
2121 r = return_data(j, f, o, data, size);
2125 j->current_field ++;
2130 _public_ void sd_journal_restart_data(sd_journal *j) {
2134 j->current_field = 0;
2137 _public_ int sd_journal_get_fd(sd_journal *j) {
2140 assert_return(j, -EINVAL);
2141 assert_return(!journal_pid_changed(j), -ECHILD);
2143 if (j->inotify_fd >= 0)
2144 return j->inotify_fd;
2146 r = allocate_inotify(j);
2150 /* Iterate through all dirs again, to add them to the
2152 if (j->no_new_files)
2153 r = add_current_paths(j);
2155 r = add_root_directory(j, j->path);
2157 r = add_search_paths(j);
2161 return j->inotify_fd;
2164 _public_ int sd_journal_get_events(sd_journal *j) {
2167 assert_return(j, -EINVAL);
2168 assert_return(!journal_pid_changed(j), -ECHILD);
2170 fd = sd_journal_get_fd(j);
2177 _public_ int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec) {
2180 assert_return(j, -EINVAL);
2181 assert_return(!journal_pid_changed(j), -ECHILD);
2182 assert_return(timeout_usec, -EINVAL);
2184 fd = sd_journal_get_fd(j);
2188 if (!j->on_network) {
2189 *timeout_usec = (uint64_t) -1;
2193 /* If we are on the network we need to regularly check for
2194 * changes manually */
2196 *timeout_usec = j->last_process_usec + JOURNAL_FILES_RECHECK_USEC;
2200 static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
2207 /* Is this a subdirectory we watch? */
2208 d = hashmap_get(j->directories_by_wd, INT_TO_PTR(e->wd));
2212 if (!(e->mask & IN_ISDIR) && e->len > 0 &&
2213 (endswith(e->name, ".journal") ||
2214 endswith(e->name, ".journal~"))) {
2216 /* Event for a journal file */
2218 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
2219 r = add_file(j, d->path, e->name);
2221 log_debug("Failed to add file %s/%s: %s",
2222 d->path, e->name, strerror(-r));
2223 set_put_error(j, r);
2226 } else if (e->mask & (IN_DELETE|IN_MOVED_FROM|IN_UNMOUNT)) {
2228 r = remove_file(j, d->path, e->name);
2230 log_debug("Failed to remove file %s/%s: %s", d->path, e->name, strerror(-r));
2233 } else if (!d->is_root && e->len == 0) {
2235 /* Event for a subdirectory */
2237 if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) {
2238 r = remove_directory(j, d);
2240 log_debug("Failed to remove directory %s: %s", d->path, strerror(-r));
2244 } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
2246 /* Event for root directory */
2248 if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
2249 r = add_directory(j, d->path, e->name);
2251 log_debug("Failed to add directory %s/%s: %s", d->path, e->name, strerror(-r));
2258 if (e->mask & IN_IGNORED)
2261 log_warning("Unknown inotify event.");
2264 static int determine_change(sd_journal *j) {
2269 b = j->current_invalidate_counter != j->last_invalidate_counter;
2270 j->last_invalidate_counter = j->current_invalidate_counter;
2272 return b ? SD_JOURNAL_INVALIDATE : SD_JOURNAL_APPEND;
2275 _public_ int sd_journal_process(sd_journal *j) {
2276 uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX] _alignas_(struct inotify_event);
2277 bool got_something = false;
2279 assert_return(j, -EINVAL);
2280 assert_return(!journal_pid_changed(j), -ECHILD);
2282 j->last_process_usec = now(CLOCK_MONOTONIC);
2285 struct inotify_event *e;
2288 l = read(j->inotify_fd, buffer, sizeof(buffer));
2290 if (errno == EAGAIN || errno == EINTR)
2291 return got_something ? determine_change(j) : SD_JOURNAL_NOP;
2296 got_something = true;
2298 e = (struct inotify_event*) buffer;
2302 process_inotify_event(j, e);
2304 step = sizeof(struct inotify_event) + e->len;
2305 assert(step <= (size_t) l);
2307 e = (struct inotify_event*) ((uint8_t*) e + step);
2313 _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) {
2317 assert_return(j, -EINVAL);
2318 assert_return(!journal_pid_changed(j), -ECHILD);
2320 if (j->inotify_fd < 0) {
2322 /* This is the first invocation, hence create the
2324 r = sd_journal_get_fd(j);
2328 /* The journal might have changed since the context
2329 * object was created and we weren't watching before,
2330 * hence don't wait for anything, and return
2332 return determine_change(j);
2335 r = sd_journal_get_timeout(j, &t);
2339 if (t != (uint64_t) -1) {
2342 n = now(CLOCK_MONOTONIC);
2343 t = t > n ? t - n : 0;
2345 if (timeout_usec == (uint64_t) -1 || timeout_usec > t)
2350 r = fd_wait_for_event(j->inotify_fd, POLLIN, timeout_usec);
2351 } while (r == -EINTR);
2356 return sd_journal_process(j);
2359 _public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) {
2363 uint64_t fmin = 0, tmax = 0;
2366 assert_return(j, -EINVAL);
2367 assert_return(!journal_pid_changed(j), -ECHILD);
2368 assert_return(from || to, -EINVAL);
2369 assert_return(from != to, -EINVAL);
2371 ORDERED_HASHMAP_FOREACH(f, j->files, i) {
2374 r = journal_file_get_cutoff_realtime_usec(f, &fr, &t);
2387 fmin = MIN(fr, fmin);
2388 tmax = MAX(t, tmax);
2397 return first ? 0 : 1;
2400 _public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) {
2406 assert_return(j, -EINVAL);
2407 assert_return(!journal_pid_changed(j), -ECHILD);
2408 assert_return(from || to, -EINVAL);
2409 assert_return(from != to, -EINVAL);
2411 ORDERED_HASHMAP_FOREACH(f, j->files, i) {
2414 r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t);
2424 *from = MIN(fr, *from);
2439 void journal_print_header(sd_journal *j) {
2442 bool newline = false;
2446 ORDERED_HASHMAP_FOREACH(f, j->files, i) {
2452 journal_file_print_header(f);
2456 _public_ int sd_journal_get_usage(sd_journal *j, uint64_t *bytes) {
2461 assert_return(j, -EINVAL);
2462 assert_return(!journal_pid_changed(j), -ECHILD);
2463 assert_return(bytes, -EINVAL);
2465 ORDERED_HASHMAP_FOREACH(f, j->files, i) {
2468 if (fstat(f->fd, &st) < 0)
2471 sum += (uint64_t) st.st_blocks * 512ULL;
2478 _public_ int sd_journal_query_unique(sd_journal *j, const char *field) {
2481 assert_return(j, -EINVAL);
2482 assert_return(!journal_pid_changed(j), -ECHILD);
2483 assert_return(!isempty(field), -EINVAL);
2484 assert_return(field_is_valid(field), -EINVAL);
2490 free(j->unique_field);
2491 j->unique_field = f;
2492 j->unique_file = NULL;
2493 j->unique_offset = 0;
2494 j->unique_file_lost = false;
2499 _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) {
2502 assert_return(j, -EINVAL);
2503 assert_return(!journal_pid_changed(j), -ECHILD);
2504 assert_return(data, -EINVAL);
2505 assert_return(l, -EINVAL);
2506 assert_return(j->unique_field, -EINVAL);
2508 k = strlen(j->unique_field);
2510 if (!j->unique_file) {
2511 if (j->unique_file_lost)
2514 j->unique_file = ordered_hashmap_first(j->files);
2515 if (!j->unique_file)
2518 j->unique_offset = 0;
2529 void *release_cookie;
2531 /* Proceed to next data object in the field's linked list */
2532 if (j->unique_offset == 0) {
2533 r = journal_file_find_field_object(j->unique_file, j->unique_field, k, &o, NULL);
2537 j->unique_offset = r > 0 ? le64toh(o->field.head_data_offset) : 0;
2539 r = journal_file_move_to_object(j->unique_file, OBJECT_DATA, j->unique_offset, &o);
2543 j->unique_offset = le64toh(o->data.next_field_offset);
2546 /* We reached the end of the list? Then start again, with the next file */
2547 if (j->unique_offset == 0) {
2548 j->unique_file = ordered_hashmap_next(j->files, j->unique_file->path);
2549 if (!j->unique_file)
2555 /* We do not use the type context here, but 0 instead,
2556 * so that we can look at this data object at the same
2557 * time as one on another file */
2558 r = journal_file_move_to_object(j->unique_file, 0, j->unique_offset, &o);
2562 /* Let's do the type check by hand, since we used 0 context above. */
2563 if (o->object.type != OBJECT_DATA) {
2564 log_debug("%s:offset " OFSfmt ": object has type %d, expected %d",
2565 j->unique_file->path, j->unique_offset,
2566 o->object.type, OBJECT_DATA);
2570 r = journal_file_object_keep(j->unique_file, o, j->unique_offset, &release_cookie);
2574 r = return_data(j, j->unique_file, o, &odata, &ol);
2578 /* Check if we have at least the field name and "=". */
2580 log_debug("%s:offset " OFSfmt ": object has size %zu, expected at least %zu",
2581 j->unique_file->path, j->unique_offset,
2586 if (memcmp(odata, j->unique_field, k) || ((const char*) odata)[k] != '=') {
2587 log_debug("%s:offset " OFSfmt ": object does not start with \"%s=\"",
2588 j->unique_file->path, j->unique_offset,
2593 /* OK, now let's see if we already returned this data
2594 * object by checking if it exists in the earlier
2595 * traversed files. */
2597 ORDERED_HASHMAP_FOREACH(of, j->files, i) {
2601 if (of == j->unique_file)
2604 /* Skip this file it didn't have any fields
2606 if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) &&
2607 le64toh(of->header->n_fields) <= 0)
2610 r = journal_file_find_data_object_with_hash(of, odata, ol, le64toh(o->data.hash), &oo, &op);
2618 r = journal_file_object_release(j->unique_file, release_cookie);
2625 r = return_data(j, j->unique_file, o, data, l);
2633 _public_ void sd_journal_restart_unique(sd_journal *j) {
2637 j->unique_file = NULL;
2638 j->unique_offset = 0;
2639 j->unique_file_lost = false;
2642 _public_ int sd_journal_reliable_fd(sd_journal *j) {
2643 assert_return(j, -EINVAL);
2644 assert_return(!journal_pid_changed(j), -ECHILD);
2646 return !j->on_network;
2649 static char *lookup_field(const char *field, void *userdata) {
2650 sd_journal *j = userdata;
2658 r = sd_journal_get_data(j, field, &data, &size);
2660 size > REPLACE_VAR_MAX)
2661 return strdup(field);
2663 d = strlen(field) + 1;
2665 return strndup((const char*) data + d, size - d);
2668 _public_ int sd_journal_get_catalog(sd_journal *j, char **ret) {
2672 _cleanup_free_ char *text = NULL, *cid = NULL;
2676 assert_return(j, -EINVAL);
2677 assert_return(!journal_pid_changed(j), -ECHILD);
2678 assert_return(ret, -EINVAL);
2680 r = sd_journal_get_data(j, "MESSAGE_ID", &data, &size);
2684 cid = strndup((const char*) data + 11, size - 11);
2688 r = sd_id128_from_string(cid, &id);
2692 r = catalog_get(CATALOG_DATABASE, id, &text);
2696 t = replace_var(text, lookup_field, j);
2704 _public_ int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **ret) {
2705 assert_return(ret, -EINVAL);
2707 return catalog_get(CATALOG_DATABASE, id, ret);
2710 _public_ int sd_journal_set_data_threshold(sd_journal *j, size_t sz) {
2711 assert_return(j, -EINVAL);
2712 assert_return(!journal_pid_changed(j), -ECHILD);
2714 j->data_threshold = sz;
2718 _public_ int sd_journal_get_data_threshold(sd_journal *j, size_t *sz) {
2719 assert_return(j, -EINVAL);
2720 assert_return(!journal_pid_changed(j), -ECHILD);
2721 assert_return(sz, -EINVAL);
2723 *sz = j->data_threshold;