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 typedef struct Match Match;
40 LIST_FIELDS(Match, matches);
46 JournalFile *current_file;
47 uint64_t current_field;
49 LIST_HEAD(Match, matches);
53 int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
69 m->data = malloc(m->size);
75 memcpy(m->data, data, size);
76 m->le_hash = hash64(m->data, size);
78 LIST_PREPEND(Match, matches, j->matches, m);
84 void sd_journal_flush_matches(sd_journal *j) {
88 Match *m = j->matches;
90 LIST_REMOVE(Match, matches, j->matches, m);
98 static int compare_order(JournalFile *af, Object *ao, uint64_t ap,
99 JournalFile *bf, Object *bo, uint64_t bp) {
103 /* We operate on two different files here, hence we can access
104 * two objects at the same time, which we normally can't.
106 * If contents and timestamps match, these entries are
107 * identical, even if the seqnum does not match */
109 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
110 ao->entry.monotonic == bo->entry.monotonic &&
111 ao->entry.realtime == bo->entry.realtime &&
112 ao->entry.xor_hash == bo->entry.xor_hash)
115 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
117 /* If this is from the same seqnum source, compare
119 a = le64toh(ao->entry.seqnum);
120 b = le64toh(bo->entry.seqnum);
127 /* Wow! This is weird, different data but the same
128 * seqnums? Something is borked, but let's make the
129 * best of it and compare by time. */
132 if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
134 /* If the boot id matches compare monotonic time */
135 a = le64toh(ao->entry.monotonic);
136 b = le64toh(bo->entry.monotonic);
144 /* Otherwise compare UTC time */
145 a = le64toh(ao->entry.realtime);
146 b = le64toh(ao->entry.realtime);
153 /* Finally, compare by contents */
154 a = le64toh(ao->entry.xor_hash);
155 b = le64toh(ao->entry.xor_hash);
165 static int move_to_next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **o, uint64_t *p) {
176 /* No matches is easy, just go on to the next entry */
178 if (f->current_offset > 0) {
179 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &c);
185 return journal_file_next_entry(f, c, direction, o, p);
188 /* So there are matches we have to adhere to, let's find the
189 * first entry that matches all of them */
191 if (f->current_offset > 0)
192 cp = f->current_offset;
194 r = journal_file_find_first_entry(f, j->matches->data, j->matches->size, direction, &c, &cp);
198 /* We can shortcut this if there's only one match */
199 if (j->n_matches == 1) {
211 r = journal_file_move_to_object(f, cp, OBJECT_ENTRY, &c);
215 n = journal_file_entry_n_items(c);
217 /* Make sure we don't match the entry we are starting
219 found = f->current_offset != cp;
222 LIST_FOREACH(matches, m, j->matches) {
225 for (k = 0; k < n; k++)
226 if (c->entry.items[k].hash == m->le_hash)
230 /* Hmm, didn't find any field that matched, so ignore
231 * this match. Go on with next match */
237 /* Hmm, so, this field matched, let's remember
238 * where we'd have to try next, in case the other
239 * matches are not OK */
241 if (direction == DIRECTION_DOWN) {
242 q = le64toh(c->entry.items[k].next_entry_offset);
247 q = le64toh(c->entry.items[k].prev_entry_offset);
249 if (q != 0 && (np == 0 || q < np))
254 /* Did this entry match against all matches? */
261 /* Did we find a subsequent entry? */
265 /* Hmm, ok, this entry only matched partially, so
266 * let's try another one */
271 static int real_journal_next(sd_journal *j, direction_t direction) {
272 JournalFile *f, *new_current = NULL;
275 uint64_t new_offset = 0;
276 Object *new_entry = NULL;
280 HASHMAP_FOREACH(f, j->files, i) {
284 r = move_to_next_with_matches(j, f, direction, &o, &p);
291 compare_order(new_current, new_entry, new_offset, f, o, p) > 0) {
299 j->current_file = new_current;
300 j->current_file->current_offset = new_offset;
301 j->current_field = 0;
303 /* Skip over any identical entries in the other files too */
305 HASHMAP_FOREACH(f, j->files, i) {
309 if (j->current_file == f)
312 r = move_to_next_with_matches(j, f, direction, &o, &p);
318 if (compare_order(new_current, new_entry, new_offset, f, o, p) == 0)
319 f->current_offset = p;
328 int sd_journal_next(sd_journal *j) {
329 return real_journal_next(j, DIRECTION_DOWN);
332 int sd_journal_previous(sd_journal *j) {
333 return real_journal_next(j, DIRECTION_UP);
336 int sd_journal_get_cursor(sd_journal *j, char **cursor) {
339 char bid[33], sid[33];
344 if (!j->current_file || j->current_file->current_offset <= 0)
345 return -EADDRNOTAVAIL;
347 r = journal_file_move_to_object(j->current_file, j->current_file->current_offset, OBJECT_ENTRY, &o);
351 sd_id128_to_string(j->current_file->header->seqnum_id, sid);
352 sd_id128_to_string(o->entry.boot_id, bid);
355 "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s",
356 sid, (unsigned long long) le64toh(o->entry.seqnum),
357 bid, (unsigned long long) le64toh(o->entry.monotonic),
358 (unsigned long long) le64toh(o->entry.realtime),
359 (unsigned long long) le64toh(o->entry.xor_hash),
360 file_name_from_path(j->current_file->path)) < 0)
366 int sd_journal_set_cursor(sd_journal *j, const char *cursor) {
370 static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) {
380 fn = join(prefix, "/", dir, "/", filename, NULL);
382 fn = join(prefix, "/", filename, NULL);
387 r = journal_file_open(fn, O_RDONLY, 0, NULL, &f);
397 r = hashmap_put(j->files, f->path, f);
399 journal_file_close(f);
406 static int add_directory(sd_journal *j, const char *prefix, const char *dir) {
415 fn = join(prefix, "/", dir, NULL);
430 struct dirent buf, *de;
432 r = readdir_r(d, &buf, &de);
436 if (!dirent_is_file_with_suffix(de, ".journal"))
439 r = add_file(j, prefix, dir, de->d_name);
441 log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r));
449 int sd_journal_open(sd_journal **ret) {
452 const char search_paths[] =
454 "/var/log/journal\0";
459 j = new0(sd_journal, 1);
463 j->files = hashmap_new(string_hash_func, string_compare_func);
469 /* We ignore most errors here, since the idea is to only open
470 * what's actually accessible, and ignore the rest. */
472 NULSTR_FOREACH(p, search_paths) {
478 log_debug("Failed to open %s: %m", p);
483 struct dirent buf, *de;
486 r = readdir_r(d, &buf, &de);
490 if (dirent_is_file_with_suffix(de, ".journal")) {
491 r = add_file(j, p, NULL, de->d_name);
493 log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r));
495 } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) &&
496 sd_id128_from_string(de->d_name, &id) >= 0) {
498 r = add_directory(j, p, de->d_name);
500 log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r));
516 void sd_journal_close(sd_journal *j) {
522 while ((f = hashmap_steal_first(j->files)))
523 journal_file_close(f);
525 hashmap_free(j->files);
528 sd_journal_flush_matches(j);
533 int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
545 if (f->current_offset <= 0)
548 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
552 *ret = le64toh(o->entry.realtime);
556 int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret) {
569 if (f->current_offset <= 0)
572 r = sd_id128_get_boot(&id);
576 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
580 if (!sd_id128_equal(id, o->entry.boot_id))
583 *ret = le64toh(o->entry.monotonic);
588 int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
600 if (isempty(field) || strchr(field, '='))
607 if (f->current_offset <= 0)
610 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
614 field_length = strlen(field);
616 n = journal_file_entry_n_items(o);
617 for (i = 0; i < n; i++) {
621 p = le64toh(o->entry.items[i].object_offset);
622 h = o->entry.items[j->current_field].hash;
623 r = journal_file_move_to_object(f, p, OBJECT_DATA, &o);
627 if (h != o->data.hash)
630 l = le64toh(o->object.size) - offsetof(Object, data.payload);
632 if (l >= field_length+1 &&
633 memcmp(o->data.payload, field, field_length) == 0 &&
634 o->data.payload[field_length] == '=') {
638 if ((uint64_t) t != l)
641 *data = o->data.payload;
647 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
655 int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
670 if (f->current_offset <= 0)
673 r = journal_file_move_to_object(f, f->current_offset, OBJECT_ENTRY, &o);
677 n = journal_file_entry_n_items(o);
678 if (j->current_field >= n)
681 p = le64toh(o->entry.items[j->current_field].object_offset);
682 h = o->entry.items[j->current_field].hash;
683 r = journal_file_move_to_object(f, p, OBJECT_DATA, &o);
687 if (h != o->data.hash)
690 l = le64toh(o->object.size) - offsetof(Object, data.payload);
693 /* We can't read objects larger than 4G on a 32bit machine */
694 if ((uint64_t) t != l)
697 *data = o->data.payload;
705 void sd_journal_start_data(sd_journal *j) {
708 j->current_field = 0;
711 static int real_journal_seek_head(sd_journal *j, direction_t direction) {
717 j->current_file = NULL;
718 j->current_field = 0;
720 HASHMAP_FOREACH(f, j->files, i)
721 f->current_offset = 0;
723 return real_journal_next(j, direction);
726 int sd_journal_seek_head(sd_journal *j) {
727 return real_journal_seek_head(j, DIRECTION_DOWN);
730 int sd_journal_seek_tail(sd_journal *j) {
731 return real_journal_seek_head(j, DIRECTION_UP);