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 General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include "sd-journal.h"
27 #include "journal-def.h"
28 #include "journal-file.h"
33 #define JOURNAL_FILES_MAX 1024
35 typedef struct Match Match;
42 LIST_FIELDS(Match, matches);
45 typedef enum location_type {
51 typedef struct Location {
72 Location current_location;
73 JournalFile *current_file;
74 uint64_t current_field;
76 LIST_HEAD(Match, matches);
80 static void detach_location(sd_journal *j) {
86 j->current_file = NULL;
89 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, JournalFile *f, Object *o) {
103 assert(o->object.type == OBJECT_ENTRY);
105 l->type = LOCATION_DISCRETE;
106 l->seqnum = le64toh(o->entry.seqnum);
107 l->seqnum_id = f->header->seqnum_id;
108 l->realtime = le64toh(o->entry.realtime);
109 l->monotonic = le64toh(o->entry.monotonic);
110 l->boot_id = le64toh(o->entry.boot_id);
111 l->xor_hash = le64toh(o->entry.xor_hash);
113 l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true;
116 static void set_location(sd_journal *j, JournalFile *f, Object *o, uint64_t offset) {
121 init_location(&j->current_location, f, o);
124 j->current_field = 0;
126 f->current_offset = offset;
129 static int same_field(const void *_a, size_t s, const void *_b, size_t t) {
130 const uint8_t *a = _a, *b = _b;
132 bool a_good = false, b_good = false, different = false;
134 for (j = 0; j < s && j < t; j++) {
143 if (a_good && b_good)
144 return different ? 0 : 1;
150 int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
151 Match *m, *after = NULL;
161 le_hash = htole64(hash64(data, size));
163 LIST_FOREACH(matches, m, j->matches) {
166 if (m->le_hash == le_hash &&
168 memcmp(m->data, data, size) == 0)
171 r = same_field(data, size, m->data, m->size);
184 m->data = malloc(m->size);
190 memcpy(m->data, data, size);
191 m->le_hash = le_hash;
193 /* Matches for the same fields we order adjacent to each
195 LIST_INSERT_AFTER(Match, matches, j->matches, after, m);
203 void sd_journal_flush_matches(sd_journal *j) {
207 Match *m = j->matches;
209 LIST_REMOVE(Match, matches, j->matches, m);
219 static int compare_order(JournalFile *af, Object *ao,
220 JournalFile *bf, Object *bo) {
229 /* We operate on two different files here, hence we can access
230 * two objects at the same time, which we normally can't.
232 * If contents and timestamps match, these entries are
233 * identical, even if the seqnum does not match */
235 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
236 ao->entry.monotonic == bo->entry.monotonic &&
237 ao->entry.realtime == bo->entry.realtime &&
238 ao->entry.xor_hash == bo->entry.xor_hash)
241 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
243 /* If this is from the same seqnum source, compare
245 a = le64toh(ao->entry.seqnum);
246 b = le64toh(bo->entry.seqnum);
253 /* Wow! This is weird, different data but the same
254 * seqnums? Something is borked, but let's make the
255 * best of it and compare by time. */
258 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
260 /* If the boot id matches compare monotonic time */
261 a = le64toh(ao->entry.monotonic);
262 b = le64toh(bo->entry.monotonic);
270 /* Otherwise compare UTC time */
271 a = le64toh(ao->entry.realtime);
272 b = le64toh(ao->entry.realtime);
279 /* Finally, compare by contents */
280 a = le64toh(ao->entry.xor_hash);
281 b = le64toh(ao->entry.xor_hash);
291 static int compare_with_location(JournalFile *af, Object *ao, Location *l) {
297 assert(l->type == LOCATION_DISCRETE);
299 if (l->monotonic_set &&
300 sd_id128_equal(ao->entry.boot_id, l->boot_id) &&
302 le64toh(ao->entry.realtime) == l->realtime &&
304 le64toh(ao->entry.xor_hash) == l->xor_hash)
308 sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) {
310 a = le64toh(ao->entry.seqnum);
318 if (l->monotonic_set &&
319 sd_id128_equal(ao->entry.boot_id, l->boot_id)) {
321 a = le64toh(ao->entry.monotonic);
323 if (a < l->monotonic)
325 if (a > l->monotonic)
329 if (l->realtime_set) {
331 a = le64toh(ao->entry.realtime);
339 if (l->xor_hash_set) {
340 a = le64toh(ao->entry.xor_hash);
351 static int find_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
359 /* No matches is simple */
361 if (j->current_location.type == LOCATION_HEAD)
362 r = journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p);
363 else if (j->current_location.type == LOCATION_TAIL)
364 r = journal_file_next_entry(f, NULL, 0, DIRECTION_UP, &o, &p);
365 else if (j->current_location.seqnum_set &&
366 sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
367 r = journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, &o, &p);
368 else if (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 else if (j->current_location.realtime_set)
371 r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p);
373 r = journal_file_next_entry(f, NULL, 0, direction, &o, &p);
379 Match *m, *term_match = NULL;
383 /* We have matches, first, let's jump to the monotonic
384 * position if we have any, since it implies a
387 if (j->current_location.type == LOCATION_DISCRETE &&
388 j->current_location.monotonic_set) {
390 r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p);
395 LIST_FOREACH(matches, m, j->matches) {
399 r = journal_file_find_data_object_with_hash(f, m->data, m->size, m->le_hash, &d, &dp);
403 if (j->current_location.type == LOCATION_HEAD)
404 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, &c, &cp);
405 else if (j->current_location.type == LOCATION_TAIL)
406 r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, &c, &cp);
407 else if (j->current_location.seqnum_set &&
408 sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
409 r = journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, &c, &cp);
410 else if (j->current_location.realtime_set)
411 r = journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, &c, &cp);
413 r = journal_file_next_entry_for_data(f, NULL, 0, dp, direction, &c, &cp);
422 } else if (same_field(term_match->data, term_match->size, m->data, m->size)) {
424 /* Same field as previous match... */
427 /* Find the earliest of the OR matches */
430 (direction == DIRECTION_DOWN && cp < tp) ||
431 (direction == DIRECTION_UP && cp > tp)) {
440 /* Previous term is finished, did anything match? */
444 /* Find the last of the AND matches */
446 (direction == DIRECTION_DOWN && tp > p) ||
447 (direction == DIRECTION_UP && tp < p)) {
464 /* Last term is finished, did anything match? */
469 (direction == DIRECTION_DOWN && tp > p) ||
470 (direction == DIRECTION_UP && tp < p)) {
488 static int next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
502 /* No matches is easy */
504 r = journal_file_next_entry(f, c, cp, direction, &c, &cp);
515 /* So there are matches we have to adhere to, let's find the
516 * first entry that matches all of them */
520 bool found, term_result = false;
521 Match *m, *term_match = NULL;
523 n = journal_file_entry_n_items(c);
525 /* Make sure we don't match the entry we are starting
527 found = cp > *offset;
530 LIST_FOREACH(matches, m, j->matches) {
533 /* Let's check if this is the beginning of a
534 * new term, i.e. has a different field prefix
535 * as the preceeding match. */
539 } else if (!same_field(term_match->data, term_match->size, m->data, m->size)) {
547 for (k = 0; k < n; k++)
548 if (c->entry.items[k].hash == m->le_hash)
552 /* Hmm, didn't find any field that
553 * matched this rule, so ignore this
554 * match. Go on with next match */
560 /* Hmm, so, this field matched, let's remember
561 * where we'd have to try next, in case the other
562 * matches are not OK */
564 r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, NULL, &q);
567 if (direction == DIRECTION_DOWN) {
571 if (np == 0 || q < np)
577 /* Check the last term */
578 if (term_match && term_result)
581 /* Did this entry match against all matches? */
590 /* Did we find a subsequent entry? */
594 /* Hmm, ok, this entry only matched partially, so
595 * let's try another one */
600 static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
603 int compare_value, r;
608 if (f->current_offset > 0) {
609 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &c);
613 cp = f->current_offset;
615 r = next_with_matches(j, f, direction, &c, &cp);
621 r = find_location(j, f, direction, &c, &cp);
631 if (j->current_location.type == LOCATION_DISCRETE) {
634 k = compare_with_location(f, c, &j->current_location);
635 if (direction == DIRECTION_DOWN)
636 found = k >= compare_value;
638 found = k <= -compare_value;
650 r = next_with_matches(j, f, direction, &c, &cp);
656 static int real_journal_next(sd_journal *j, direction_t direction) {
657 JournalFile *f, *new_current = NULL;
660 uint64_t new_offset = 0;
661 Object *new_entry = NULL;
665 HASHMAP_FOREACH(f, j->files, i) {
670 r = next_beyond_location(j, f, direction, &o, &p);
681 k = compare_order(f, o, new_current, new_entry);
683 if (direction == DIRECTION_DOWN)
699 set_location(j, new_current, new_entry, new_offset);
704 int sd_journal_next(sd_journal *j) {
705 return real_journal_next(j, DIRECTION_DOWN);
708 int sd_journal_previous(sd_journal *j) {
709 return real_journal_next(j, DIRECTION_UP);
712 int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
718 r = sd_journal_next(j);
732 int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
738 r = sd_journal_previous(j);
752 int sd_journal_get_cursor(sd_journal *j, char **cursor) {
755 char bid[33], sid[33];
760 if (!j->current_file || j->current_file->current_offset <= 0)
761 return -EADDRNOTAVAIL;
763 r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
767 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
768 sd_id128_to_string(o->entry.boot_id, bid);
771 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
772 sid, (unsigned long long) le64toh(o->entry.seqnum),
773 bid, (unsigned long long) le64toh(o->entry.monotonic),
774 (unsigned long long) le64toh(o->entry.realtime),
775 (unsigned long long) le64toh(o->entry.xor_hash),
776 file_name_from_path(j->current_file->path)) < 0)
782 int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
786 unsigned long long seqnum, monotonic, realtime, xor_hash;
788 seqnum_id_set = false,
791 monotonic_set = false,
792 realtime_set = false,
793 xor_hash_set = false;
794 sd_id128_t seqnum_id, boot_id;
799 FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
803 if (l < 2 || w[1] != '=')
806 item = strndup(w, l);
813 seqnum_id_set = true;
814 k = sd_id128_from_string(w+2, &seqnum_id);
819 if (sscanf(w+2, "%llx", &seqnum) != 1)
825 k = sd_id128_from_string(w+2, &boot_id);
829 monotonic_set = true;
830 if (sscanf(w+2, "%llx", &monotonic) != 1)
836 if (sscanf(w+2, "%llx", &realtime) != 1)
842 if (sscanf(w+2, "%llx", &xor_hash) != 1)
853 if ((!seqnum_set || !seqnum_id_set) &&
854 (!monotonic_set || !boot_id_set) &&
860 j->current_location.type = LOCATION_DISCRETE;
863 j->current_location.realtime = (uint64_t) realtime;
864 j->current_location.realtime_set = true;
867 if (seqnum_set && seqnum_id_set) {
868 j->current_location.seqnum = (uint64_t) seqnum;
869 j->current_location.seqnum_id = seqnum_id;
870 j->current_location.seqnum_set = true;
873 if (monotonic_set && boot_id_set) {
874 j->current_location.monotonic = (uint64_t) monotonic;
875 j->current_location.boot_id = boot_id;
876 j->current_location.monotonic_set = true;
880 j->current_location.xor_hash = (uint64_t) xor_hash;
881 j->current_location.xor_hash_set = true;
887 int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
891 j->current_location.type = LOCATION_DISCRETE;
892 j->current_location.boot_id = boot_id;
893 j->current_location.monotonic = usec;
894 j->current_location.monotonic_set = true;
899 int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
903 j->current_location.type = LOCATION_DISCRETE;
904 j->current_location.realtime = usec;
905 j->current_location.realtime_set = true;
910 int sd_journal_seek_head(sd_journal *j) {
914 j->current_location.type = LOCATION_HEAD;
919 int sd_journal_seek_tail(sd_journal *j) {
923 j->current_location.type = LOCATION_TAIL;
928 static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
937 if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
938 log_debug("Too many open journal files, ignoring.");
943 fn = join(prefix, "/", dir, "/", filename, NULL);
945 fn = join(prefix, "/", filename, NULL);
950 r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
960 journal_file_dump(f);
962 r = hashmap_put(j->files, f->path, f);
964 journal_file_close(f);
971 static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
980 fn = join(prefix, "/", dir, NULL);
995 struct dirent buf, *de;
997 r = readdir_r(d, &buf, &de);
1001 if (!dirent_is_file_with_suffix(de, ".journal"))
1004 r = add_file(j, prefix, dir, de->d_name);
1006 log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
1014 int sd_journal_open(sd_journal **ret) {
1017 const char search_paths[] =
1018 "/run/log/journal\0"
1019 "/var/log/journal\0";
1024 j = new0(sd_journal, 1);
1028 j->files = hashmap_new(string_hash_func, string_compare_func);
1034 /* We ignore most errors here, since the idea is to only open
1035 * what's actually accessible, and ignore the rest. */
1037 NULSTR_FOREACH(p, search_paths) {
1042 if (errno != ENOENT)
1043 log_debug("Failed to open %s: %m", p);
1048 struct dirent buf, *de;
1051 r = readdir_r(d, &buf, &de);
1055 if (dirent_is_file_with_suffix(de, ".journal")) {
1056 r = add_file(j, p, NULL, de->d_name);
1058 log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
1060 } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
1061 sd_id128_from_string(de->d_name, &id) >= 0) {
1063 r = add_directory(j, p, de->d_name);
1065 log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
1076 sd_journal_close(j);
1081 void sd_journal_close(sd_journal *j) {
1087 while ((f = hashmap_steal_first(j->files)))
1088 journal_file_close(f);
1090 hashmap_free(j->files);
1093 sd_journal_flush_matches(j);
1098 int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
1106 f = j->current_file;
1108 return -EADDRNOTAVAIL;
1110 if (f->current_offset <= 0)
1111 return -EADDRNOTAVAIL;
1113 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1117 *ret = le64toh(o->entry.realtime);
1121 int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
1130 f = j->current_file;
1132 return -EADDRNOTAVAIL;
1134 if (f->current_offset <= 0)
1135 return -EADDRNOTAVAIL;
1137 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1142 *ret_boot_id = o->entry.boot_id;
1144 r = sd_id128_get_boot(&id);
1148 if (!sd_id128_equal(id, o->entry.boot_id))
1152 *ret = le64toh(o->entry.monotonic);
1156 int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
1159 size_t field_length;
1168 if (isempty(field) || strchr(field, '='))
1171 f = j->current_file;
1173 return -EADDRNOTAVAIL;
1175 if (f->current_offset <= 0)
1176 return -EADDRNOTAVAIL;
1178 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1182 field_length = strlen(field);
1184 n = journal_file_entry_n_items(o);
1185 for (i = 0; i < n; i++) {
1186 uint64_t p, l, le_hash;
1189 p = le64toh(o->entry.items[i].object_offset);
1190 le_hash = o->entry.items[j->current_field].hash;
1191 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1195 if (le_hash != o->data.hash)
1198 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1200 if (l >= field_length+1 &&
1201 memcmp(o->data.payload, field, field_length) == 0 &&
1202 o->data.payload[field_length] == '=') {
1206 if ((uint64_t) t != l)
1209 *data = o->data.payload;
1215 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1223 int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
1225 uint64_t p, l, n, le_hash;
1234 f = j->current_file;
1236 return -EADDRNOTAVAIL;
1238 if (f->current_offset <= 0)
1239 return -EADDRNOTAVAIL;
1241 r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
1245 n = journal_file_entry_n_items(o);
1246 if (j->current_field >= n)
1249 p = le64toh(o->entry.items[j->current_field].object_offset);
1250 le_hash = o->entry.items[j->current_field].hash;
1251 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1255 if (le_hash != o->data.hash)
1258 l = le64toh(o->object.size) - offsetof(Object, data.payload);
1261 /* We can't read objects larger than 4G on a 32bit machine */
1262 if ((uint64_t) t != l)
1265 *data = o->data.payload;
1268 j->current_field ++;
1273 void sd_journal_restart_data(sd_journal *j) {
1276 j->current_field = 0;