X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fjournal%2Fsd-journal.c;h=7587211506f5f02d5503dc1c730201ebf88450aa;hp=09ce949d3bd21a1f5e6d9be65f69e2af504f8274;hb=03e334a1c7dc8c20c38902aa039440763acc9b17;hpb=ab4979d202e12d60bb2e31dee32fd3e79a4453fd diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index 09ce949d3..758721150 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -24,19 +24,59 @@ #include #include #include +#include +#include +#include #include "sd-journal.h" #include "journal-def.h" #include "journal-file.h" #include "hashmap.h" #include "list.h" +#include "strv.h" #include "path-util.h" #include "lookup3.h" #include "compress.h" #include "journal-internal.h" +#include "missing.h" +#include "catalog.h" +#include "replace-var.h" +#include "fileio.h" #define JOURNAL_FILES_MAX 1024 +#define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC) + +#define REPLACE_VAR_MAX 256 + +#define DEFAULT_DATA_THRESHOLD (64*1024) + +static void remove_file_real(sd_journal *j, JournalFile *f); + +static bool journal_pid_changed(sd_journal *j) { + assert(j); + + /* We don't support people creating a journal object and + * keeping it around over a fork(). Let's complain. */ + + return j->original_pid != getpid(); +} + +/* We return an error here only if we didn't manage to + memorize the real error. */ +static int set_put_error(sd_journal *j, int r) { + int k; + + if (r >= 0) + return r; + + k = set_ensure_allocated(&j->errors, trivial_hash_func, trivial_compare_func); + if (k < 0) + return k; + + return set_put(j->errors, INT_TO_PTR(r)); +} + static void detach_location(sd_journal *j) { Iterator i; JournalFile *f; @@ -57,12 +97,13 @@ static void reset_location(sd_journal *j) { zero(j->current_location); } -static void init_location(Location *l, JournalFile *f, Object *o) { +static void init_location(Location *l, LocationType type, JournalFile *f, Object *o) { assert(l); + assert(type == LOCATION_DISCRETE || type == LOCATION_SEEK); assert(f); assert(o->object.type == OBJECT_ENTRY); - l->type = LOCATION_DISCRETE; + l->type = type; l->seqnum = le64toh(o->entry.seqnum); l->seqnum_id = f->header->seqnum_id; l->realtime = le64toh(o->entry.realtime); @@ -73,124 +114,326 @@ static void init_location(Location *l, JournalFile *f, Object *o) { l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true; } -static void set_location(sd_journal *j, JournalFile *f, Object *o, uint64_t offset) { +static void set_location(sd_journal *j, LocationType type, JournalFile *f, Object *o, + direction_t direction, uint64_t offset) { assert(j); + assert(type == LOCATION_DISCRETE || type == LOCATION_SEEK); assert(f); assert(o); - init_location(&j->current_location, f, o); + init_location(&j->current_location, type, f, o); j->current_file = f; j->current_field = 0; + f->last_direction = direction; f->current_offset = offset; } -static int same_field(const void *_a, size_t s, const void *_b, size_t t) { +static int match_is_valid(const void *data, size_t size) { + const char *b, *p; + + assert(data); + + if (size < 2) + return false; + + if (startswith(data, "__")) + return false; + + b = data; + for (p = b; p < b + size; p++) { + + if (*p == '=') + return p > b; + + if (*p == '_') + continue; + + if (*p >= 'A' && *p <= 'Z') + continue; + + if (*p >= '0' && *p <= '9') + continue; + + return false; + } + + return false; +} + +static bool same_field(const void *_a, size_t s, const void *_b, size_t t) { const uint8_t *a = _a, *b = _b; size_t j; - bool a_good = false, b_good = false, different = false; for (j = 0; j < s && j < t; j++) { - if (a[j] == '=') - a_good = true; - if (b[j] == '=') - b_good = true; if (a[j] != b[j]) - different = true; + return false; - if (a_good && b_good) - return different ? 0 : 1; + if (a[j] == '=') + return true; + } + + assert_not_reached("\"=\" not found"); +} + +static Match *match_new(Match *p, MatchType t) { + Match *m; + + m = new0(Match, 1); + if (!m) + return NULL; + + m->type = t; + + if (p) { + m->parent = p; + LIST_PREPEND(matches, p->matches, m); } - return -EINVAL; + return m; +} + +static void match_free(Match *m) { + assert(m); + + while (m->matches) + match_free(m->matches); + + if (m->parent) + LIST_REMOVE(matches, m->parent->matches, m); + + free(m->data); + free(m); +} + +static void match_free_if_empty(Match *m) { + if (!m || m->matches) + return; + + match_free(m); } _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) { - Match *m, *after = NULL; + Match *l3, *l4, *add_here = NULL, *m; le64_t le_hash; - if (!j) - return -EINVAL; - if (!data) - return -EINVAL; - if (size <= 0) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(data, -EINVAL); + + if (size == 0) + size = strlen(data); + + assert_return(match_is_valid(data, size), -EINVAL); + + /* level 0: AND term + * level 1: OR terms + * level 2: AND terms + * level 3: OR terms + * level 4: concrete matches */ + + if (!j->level0) { + j->level0 = match_new(NULL, MATCH_AND_TERM); + if (!j->level0) + return -ENOMEM; + } + + if (!j->level1) { + j->level1 = match_new(j->level0, MATCH_OR_TERM); + if (!j->level1) + return -ENOMEM; + } + + if (!j->level2) { + j->level2 = match_new(j->level1, MATCH_AND_TERM); + if (!j->level2) + return -ENOMEM; + } - /* FIXME: iterating with multiple matches is currently - * broken */ - if (j->matches) - return -ENOTSUP; + assert(j->level0->type == MATCH_AND_TERM); + assert(j->level1->type == MATCH_OR_TERM); + assert(j->level2->type == MATCH_AND_TERM); le_hash = htole64(hash64(data, size)); - LIST_FOREACH(matches, m, j->matches) { - int r; + LIST_FOREACH(matches, l3, j->level2->matches) { + assert(l3->type == MATCH_OR_TERM); - if (m->le_hash == le_hash && - m->size == size && - memcmp(m->data, data, size) == 0) - return 0; + LIST_FOREACH(matches, l4, l3->matches) { + assert(l4->type == MATCH_DISCRETE); - r = same_field(data, size, m->data, m->size); - if (r < 0) - return r; - else if (r > 0) - after = m; + /* Exactly the same match already? Then ignore + * this addition */ + if (l4->le_hash == le_hash && + l4->size == size && + memcmp(l4->data, data, size) == 0) + return 0; + + /* Same field? Then let's add this to this OR term */ + if (same_field(data, size, l4->data, l4->size)) { + add_here = l3; + break; + } + } + + if (add_here) + break; } - m = new0(Match, 1); + if (!add_here) { + add_here = match_new(j->level2, MATCH_OR_TERM); + if (!add_here) + goto fail; + } + + m = match_new(add_here, MATCH_DISCRETE); if (!m) - return -ENOMEM; + goto fail; + m->le_hash = le_hash; m->size = size; + m->data = memdup(data, size); + if (!m->data) + goto fail; - m->data = malloc(m->size); - if (!m->data) { - free(m); - return -ENOMEM; - } + detach_location(j); - memcpy(m->data, data, size); - m->le_hash = le_hash; + return 0; + +fail: + match_free_if_empty(add_here); + match_free_if_empty(j->level2); + match_free_if_empty(j->level1); + match_free_if_empty(j->level0); + + return -ENOMEM; +} - /* Matches for the same fields we order adjacent to each - * other */ - LIST_INSERT_AFTER(Match, matches, j->matches, after, m); - j->n_matches ++; +_public_ int sd_journal_add_conjunction(sd_journal *j) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); - detach_location(j); + if (!j->level0) + return 0; + + if (!j->level1) + return 0; + + if (!j->level1->matches) + return 0; + + j->level1 = NULL; + j->level2 = NULL; + + return 0; +} + +_public_ int sd_journal_add_disjunction(sd_journal *j) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + if (!j->level0) + return 0; + + if (!j->level1) + return 0; + + if (!j->level2) + return 0; + if (!j->level2->matches) + return 0; + + j->level2 = NULL; return 0; } +static char *match_make_string(Match *m) { + char *p, *r; + Match *i; + bool enclose = false; + + if (!m) + return strdup("none"); + + if (m->type == MATCH_DISCRETE) + return strndup(m->data, m->size); + + p = NULL; + LIST_FOREACH(matches, i, m->matches) { + char *t, *k; + + t = match_make_string(i); + if (!t) { + free(p); + return NULL; + } + + if (p) { + k = strjoin(p, m->type == MATCH_OR_TERM ? " OR " : " AND ", t, NULL); + free(p); + free(t); + + if (!k) + return NULL; + + p = k; + + enclose = true; + } else + p = t; + } + + if (enclose) { + r = strjoin("(", p, ")", NULL); + free(p); + return r; + } + + return p; +} + +char *journal_make_match_string(sd_journal *j) { + assert(j); + + return match_make_string(j->level0); +} + _public_ void sd_journal_flush_matches(sd_journal *j) { if (!j) return; - while (j->matches) { - Match *m = j->matches; - - LIST_REMOVE(Match, matches, j->matches, m); - free(m->data); - free(m); - } + if (j->level0) + match_free(j->level0); - j->n_matches = 0; + j->level0 = j->level1 = j->level2 = NULL; detach_location(j); } -static int compare_order(JournalFile *af, Object *ao, - JournalFile *bf, Object *bo) { +static int compare_entry_order(JournalFile *af, Object *_ao, + JournalFile *bf, uint64_t bp) { uint64_t a, b; + Object *ao, *bo; + int r; assert(af); - assert(ao); assert(bf); - assert(bo); + assert(_ao); + + /* The mmap cache might invalidate the object from the first + * file if we look at the one from the second file. Hence + * temporarily copy the header of the first one, and look at + * that only. */ + ao = alloca(offsetof(EntryObject, items)); + memcpy(ao, _ao, offsetof(EntryObject, items)); + + r = journal_file_move_to_object(bf, OBJECT_ENTRY, bp, &bo); + if (r < 0) + return strcmp(af->path, bf->path); /* We operate on two different files here, hence we can access * two objects at the same time, which we normally can't. @@ -223,7 +466,7 @@ static int compare_order(JournalFile *af, Object *ao, if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) { - /* If the boot id matches compare monotonic time */ + /* If the boot id matches, compare monotonic time */ a = le64toh(ao->entry.monotonic); b = le64toh(bo->entry.monotonic); @@ -233,9 +476,9 @@ static int compare_order(JournalFile *af, Object *ao, return 1; } - /* Otherwise compare UTC time */ + /* Otherwise, compare UTC time */ a = le64toh(ao->entry.realtime); - b = le64toh(ao->entry.realtime); + b = le64toh(bo->entry.realtime); if (a < b) return -1; @@ -244,7 +487,7 @@ static int compare_order(JournalFile *af, Object *ao, /* Finally, compare by contents */ a = le64toh(ao->entry.xor_hash); - b = le64toh(ao->entry.xor_hash); + b = le64toh(bo->entry.xor_hash); if (a < b) return -1; @@ -254,13 +497,13 @@ static int compare_order(JournalFile *af, Object *ao, return 0; } -static int compare_with_location(JournalFile *af, Object *ao, Location *l) { +_pure_ static int compare_with_location(JournalFile *af, Object *ao, Location *l) { uint64_t a; assert(af); assert(ao); assert(l); - assert(l->type == LOCATION_DISCRETE); + assert(l->type == LOCATION_DISCRETE || l->type == LOCATION_SEEK); if (l->monotonic_set && sd_id128_equal(ao->entry.boot_id, l->boot_id) && @@ -314,314 +557,291 @@ static int compare_with_location(JournalFile *af, Object *ao, Location *l) { return 0; } -static int find_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) { - Object *o = NULL; - uint64_t p = 0; +static int next_for_match( + sd_journal *j, + Match *m, + JournalFile *f, + uint64_t after_offset, + direction_t direction, + Object **ret, + uint64_t *offset) { + int r; + uint64_t np = 0; + Object *n; assert(j); + assert(m); + assert(f); - if (!j->matches) { - /* No matches is simple */ - - if (j->current_location.type == LOCATION_HEAD) - r = journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p); - else if (j->current_location.type == LOCATION_TAIL) - r = journal_file_next_entry(f, NULL, 0, DIRECTION_UP, &o, &p); - else if (j->current_location.seqnum_set && - sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) - r = journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, &o, &p); - else if (j->current_location.monotonic_set) { - r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p); - - if (r == -ENOENT) { - /* boot id unknown in this file */ - if (j->current_location.realtime_set) - r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p); - else - r = journal_file_next_entry(f, NULL, 0, direction, &o, &p); - } - } else if (j->current_location.realtime_set) - r = journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, &o, &p); - else - r = journal_file_next_entry(f, NULL, 0, direction, &o, &p); + if (m->type == MATCH_DISCRETE) { + uint64_t dp; + r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp); if (r <= 0) return r; - } else { - Match *m, *term_match = NULL; - Object *to = NULL; - uint64_t tp = 0; + return journal_file_move_to_entry_by_offset_for_data(f, dp, after_offset, direction, ret, offset); - /* We have matches, first, let's jump to the monotonic - * position if we have any, since it implies a - * match. */ + } else if (m->type == MATCH_OR_TERM) { + Match *i; - if (j->current_location.type == LOCATION_DISCRETE && - j->current_location.monotonic_set) { + /* Find the earliest match beyond after_offset */ - r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, &o, &p); - if (r <= 0) - return r == -ENOENT ? 0 : r; - } - - LIST_FOREACH(matches, m, j->matches) { - Object *c, *d; - uint64_t cp, dp; - - r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), &d, &dp); - if (r <= 0) - return r; - - if (j->current_location.type == LOCATION_HEAD) - r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, &c, &cp); - else if (j->current_location.type == LOCATION_TAIL) - r = journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, &c, &cp); - else if (j->current_location.seqnum_set && - sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) - r = journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, &c, &cp); - else if (j->current_location.realtime_set) - r = journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, &c, &cp); - else - r = journal_file_next_entry_for_data(f, NULL, 0, dp, direction, &c, &cp); + LIST_FOREACH(matches, i, m->matches) { + uint64_t cp; + r = next_for_match(j, i, f, after_offset, direction, NULL, &cp); if (r < 0) return r; + else if (r > 0) { + if (np == 0 || (direction == DIRECTION_DOWN ? cp < np : cp > np)) + np = cp; + } + } - if (!term_match) { - term_match = m; - - if (r > 0) { - to = c; - tp = cp; - } - } else if (same_field(term_match->data, term_match->size, m->data, m->size)) { - - /* Same field as previous match... */ - if (r > 0) { + if (np == 0) + return 0; - /* Find the earliest of the OR matches */ + } else if (m->type == MATCH_AND_TERM) { + Match *i, *last_moved; - if (!to || - (direction == DIRECTION_DOWN && cp < tp) || - (direction == DIRECTION_UP && cp > tp)) { - to = c; - tp = cp; - } + /* Always jump to the next matching entry and repeat + * this until we find an offset that matches for all + * matches. */ - } + if (!m->matches) + return 0; - } else { + r = next_for_match(j, m->matches, f, after_offset, direction, NULL, &np); + if (r <= 0) + return r; - /* Previous term is finished, did anything match? */ - if (!to) - return 0; + assert(direction == DIRECTION_DOWN ? np >= after_offset : np <= after_offset); + last_moved = m->matches; - /* Find the last of the AND matches */ - if (!o || - (direction == DIRECTION_DOWN && tp > p) || - (direction == DIRECTION_UP && tp < p)) { - o = to; - p = tp; - } + LIST_LOOP_BUT_ONE(matches, i, m->matches, last_moved) { + uint64_t cp; - term_match = m; + r = next_for_match(j, i, f, np, direction, NULL, &cp); + if (r <= 0) + return r; - if (r > 0) { - to = c; - tp = cp; - } else { - to = NULL; - tp = 0; - } + assert(direction == DIRECTION_DOWN ? cp >= np : cp <= np); + if (direction == DIRECTION_DOWN ? cp > np : cp < np) { + np = cp; + last_moved = i; } } + } - /* Last term is finished, did anything match? */ - if (!to) - return 0; - - if (!o || - (direction == DIRECTION_DOWN && tp > p) || - (direction == DIRECTION_UP && tp < p)) { - o = to; - p = tp; - } + assert(np > 0); - if (!o) - return 0; - } + r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n); + if (r < 0) + return r; if (ret) - *ret = o; - + *ret = n; if (offset) - *offset = p; + *offset = np; return 1; } -static int next_with_matches(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) { +static int find_location_for_match( + sd_journal *j, + Match *m, + JournalFile *f, + direction_t direction, + Object **ret, + uint64_t *offset) { + int r; - uint64_t cp; - Object *c; assert(j); + assert(m); assert(f); - assert(ret); - assert(offset); - - c = *ret; - cp = *offset; - if (!j->matches) { - /* No matches is easy */ + if (m->type == MATCH_DISCRETE) { + uint64_t dp; - r = journal_file_next_entry(f, c, cp, direction, &c, &cp); + r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp); if (r <= 0) return r; - if (ret) - *ret = c; - if (offset) - *offset = cp; - return 1; - } + /* FIXME: missing: find by monotonic */ - /* So there are matches we have to adhere to, let's find the - * first entry that matches all of them */ - - for (;;) { - uint64_t np, n; - bool found, term_result = false; - Match *m, *term_match = NULL; - Object *npo = NULL; - - n = journal_file_entry_n_items(c); - - /* Make sure we don't match the entry we are starting - * from. */ - found = cp != *offset; - - np = 0; - LIST_FOREACH(matches, m, j->matches) { - uint64_t q, k; - Object *qo = NULL; - - /* Let's check if this is the beginning of a - * new term, i.e. has a different field prefix - * as the preceeding match. */ - if (!term_match) { - term_match = m; - term_result = false; - } else if (!same_field(term_match->data, term_match->size, m->data, m->size)) { - if (!term_result) - found = false; - - term_match = m; - term_result = false; - } + if (j->current_location.type == LOCATION_HEAD) + return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, ret, offset); + if (j->current_location.type == LOCATION_TAIL) + return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, ret, offset); + if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) + return journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, ret, offset); + if (j->current_location.monotonic_set) { + r = journal_file_move_to_entry_by_monotonic_for_data(f, dp, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset); + if (r != -ENOENT) + return r; + } + if (j->current_location.realtime_set) + return journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, ret, offset); - for (k = 0; k < n; k++) - if (c->entry.items[k].hash == m->le_hash) - break; + return journal_file_next_entry_for_data(f, NULL, 0, dp, direction, ret, offset); - if (k >= n) { - /* Hmm, didn't find any field that - * matched this rule, so ignore this - * match. Go on with next match */ - continue; - } + } else if (m->type == MATCH_OR_TERM) { + uint64_t np = 0; + Object *n; + Match *i; - term_result = true; + /* Find the earliest match */ - /* Hmm, so, this field matched, let's remember - * where we'd have to try next, in case the other - * matches are not OK */ + LIST_FOREACH(matches, i, m->matches) { + uint64_t cp; - r = journal_file_next_entry_for_data(f, c, cp, le64toh(c->entry.items[k].object_offset), direction, &qo, &q); - /* This pointer is invalidated if the window was - * remapped. May need to re-fetch it later */ - c = NULL; + r = find_location_for_match(j, i, f, direction, NULL, &cp); if (r < 0) return r; - - if (r > 0) { - - if (direction == DIRECTION_DOWN) { - if (q > np) { - np = q; - npo = qo; - } - } else { - if (np == 0 || q < np) { - np = q; - npo = qo; - } - } + else if (r > 0) { + if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp)) + np = cp; } } - /* Check the last term */ - if (term_match && !term_result) - found = false; + if (np == 0) + return 0; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n); + if (r < 0) + return r; - /* Did this entry match against all matches? */ - if (found) { - if (ret) { - if (c == NULL) { - /* Re-fetch the entry */ - r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c); - if (r < 0) - return r; - } - *ret = c; - } - if (offset) - *offset = cp; - return 1; - } + if (ret) + *ret = n; + if (offset) + *offset = np; - /* Did we find a subsequent entry? */ - if (np == 0) + return 1; + + } else { + Match *i; + uint64_t np = 0; + + assert(m->type == MATCH_AND_TERM); + + /* First jump to the last match, and then find the + * next one where all matches match */ + + if (!m->matches) return 0; - /* Hmm, ok, this entry only matched partially, so - * let's try another one */ - cp = np; - c = npo; + LIST_FOREACH(matches, i, m->matches) { + uint64_t cp; + + r = find_location_for_match(j, i, f, direction, NULL, &cp); + if (r <= 0) + return r; + + if (np == 0 || (direction == DIRECTION_DOWN ? cp > np : cp < np)) + np = cp; + } + + return next_for_match(j, m, f, np, direction, ret, offset); } } -static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) { - Object *c; - uint64_t cp; - int compare_value, r; +static int find_location_with_matches( + sd_journal *j, + JournalFile *f, + direction_t direction, + Object **ret, + uint64_t *offset) { + + int r; assert(j); assert(f); + assert(ret); + assert(offset); - if (f->current_offset > 0) { - cp = f->current_offset; + if (!j->level0) { + /* No matches is simple */ - r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c); - if (r < 0) - return r; + if (j->current_location.type == LOCATION_HEAD) + return journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, ret, offset); + if (j->current_location.type == LOCATION_TAIL) + return journal_file_next_entry(f, NULL, 0, DIRECTION_UP, ret, offset); + if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id)) + return journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, ret, offset); + if (j->current_location.monotonic_set) { + r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset); + if (r != -ENOENT) + return r; + } + if (j->current_location.realtime_set) + return journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, ret, offset); - r = next_with_matches(j, f, direction, &c, &cp); - if (r <= 0) - return r; + return journal_file_next_entry(f, NULL, 0, direction, ret, offset); + } else + return find_location_for_match(j, j->level0, f, direction, ret, offset); +} - compare_value = 1; - } else { - r = find_location(j, f, direction, &c, &cp); - if (r <= 0) - return r; +static int next_with_matches( + sd_journal *j, + JournalFile *f, + direction_t direction, + Object **ret, + uint64_t *offset) { - compare_value = 0; + Object *c; + uint64_t cp; + + assert(j); + assert(f); + assert(ret); + assert(offset); + + c = *ret; + cp = *offset; + + /* No matches is easy. We simple advance the file + * pointer by one. */ + if (!j->level0) + return journal_file_next_entry(f, c, cp, direction, ret, offset); + + /* If we have a match then we look for the next matching entry + * with an offset at least one step larger */ + return next_for_match(j, j->level0, f, direction == DIRECTION_DOWN ? cp+1 : cp-1, direction, ret, offset); +} + +static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) { + Object *c; + uint64_t cp; + int r; + + assert(j); + assert(f); + + if (f->last_direction == direction && f->current_offset > 0) { + cp = f->current_offset; + + r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c); + if (r < 0) + return r; + + r = next_with_matches(j, f, direction, &c, &cp); + if (r <= 0) + return r; + } else { + r = find_location_with_matches(j, f, direction, &c, &cp); + if (r <= 0) + return r; } + /* OK, we found the spot, now let's advance until an entry + * that is actually different from what we were previously + * looking at. This is necessary to handle entries which exist + * in two (or more) journal files, and which shall all be + * suppressed but one. */ + for (;;) { bool found; @@ -630,9 +850,9 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc k = compare_with_location(f, c, &j->current_location); if (direction == DIRECTION_DOWN) - found = k >= compare_value; + found = k > 0; else - found = k <= -compare_value; + found = k < 0; } else found = true; @@ -651,50 +871,51 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc } static int real_journal_next(sd_journal *j, direction_t direction) { - JournalFile *f, *new_current = NULL; + JournalFile *f, *new_file = NULL; + uint64_t new_offset = 0; + uint64_t p = 0; Iterator i; + Object *o; int r; - uint64_t new_offset = 0; - Object *new_entry = NULL; - if (!j) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); HASHMAP_FOREACH(f, j->files, i) { - Object *o; - uint64_t p; bool found; r = next_beyond_location(j, f, direction, &o, &p); - if (r < 0) - return r; - else if (r == 0) + if (r < 0) { + log_debug("Can't iterate through %s, ignoring: %s", f->path, strerror(-r)); + remove_file_real(j, f); + continue; + } else if (r == 0) continue; - if (!new_current) + if (!new_file) found = true; else { int k; - k = compare_order(f, o, new_current, new_entry); + k = compare_entry_order(f, o, new_file, new_offset); - if (direction == DIRECTION_DOWN) - found = k < 0; - else - found = k > 0; + found = direction == DIRECTION_DOWN ? k < 0 : k > 0; } if (found) { - new_current = f; - new_entry = o; + new_file = f; new_offset = p; } } - if (!new_current) + if (!new_file) return 0; - set_location(j, new_current, new_entry, new_offset); + r = journal_file_move_to_object(new_file, OBJECT_ENTRY, new_offset, &o); + if (r < 0) + return r; + + set_location(j, LOCATION_DISCRETE, new_file, o, direction, new_offset); return 1; } @@ -710,8 +931,8 @@ _public_ int sd_journal_previous(sd_journal *j) { static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) { int c = 0, r; - if (!j) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); if (skip == 0) { /* If this is not a discrete skip, then at least @@ -750,10 +971,9 @@ _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) { int r; char bid[33], sid[33]; - if (!j) - return -EINVAL; - if (!cursor) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(cursor, -EINVAL); if (!j->current_file || j->current_file->current_offset <= 0) return -EADDRNOTAVAIL; @@ -766,21 +986,19 @@ _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) { sd_id128_to_string(o->entry.boot_id, bid); if (asprintf(cursor, - "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx;p=%s", - sid, (unsigned long long) le64toh(o->entry.seqnum), - bid, (unsigned long long) le64toh(o->entry.monotonic), - (unsigned long long) le64toh(o->entry.realtime), - (unsigned long long) le64toh(o->entry.xor_hash), - path_get_file_name(j->current_file->path)) < 0) + "s=%s;i=%"PRIx64";b=%s;m=%"PRIx64";t=%"PRIx64";x=%"PRIx64, + sid, le64toh(o->entry.seqnum), + bid, le64toh(o->entry.monotonic), + le64toh(o->entry.realtime), + le64toh(o->entry.xor_hash)) < 0) return -ENOMEM; - return 1; + return 0; } _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { - char *w; + char *w, *state; size_t l; - char *state; unsigned long long seqnum, monotonic, realtime, xor_hash; bool seqnum_id_set = false, @@ -791,10 +1009,9 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { xor_hash_set = false; sd_id128_t seqnum_id, boot_id; - if (!j) - return -EINVAL; - if (!cursor) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(!isempty(cursor), -EINVAL); FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) { char *item; @@ -811,35 +1028,35 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { case 's': seqnum_id_set = true; - k = sd_id128_from_string(w+2, &seqnum_id); + k = sd_id128_from_string(item+2, &seqnum_id); break; case 'i': seqnum_set = true; - if (sscanf(w+2, "%llx", &seqnum) != 1) + if (sscanf(item+2, "%llx", &seqnum) != 1) k = -EINVAL; break; case 'b': boot_id_set = true; - k = sd_id128_from_string(w+2, &boot_id); + k = sd_id128_from_string(item+2, &boot_id); break; case 'm': monotonic_set = true; - if (sscanf(w+2, "%llx", &monotonic) != 1) + if (sscanf(item+2, "%llx", &monotonic) != 1) k = -EINVAL; break; case 't': realtime_set = true; - if (sscanf(w+2, "%llx", &realtime) != 1) + if (sscanf(item+2, "%llx", &realtime) != 1) k = -EINVAL; break; case 'x': xor_hash_set = true; - if (sscanf(w+2, "%llx", &xor_hash) != 1) + if (sscanf(item+2, "%llx", &xor_hash) != 1) k = -EINVAL; break; } @@ -857,7 +1074,7 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { reset_location(j); - j->current_location.type = LOCATION_DISCRETE; + j->current_location.type = LOCATION_SEEK; if (realtime_set) { j->current_location.realtime = (uint64_t) realtime; @@ -884,12 +1101,94 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) { return 0; } +_public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) { + int r; + char *w, *state; + size_t l; + Object *o; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(!isempty(cursor), -EINVAL); + + if (!j->current_file || j->current_file->current_offset <= 0) + return -EADDRNOTAVAIL; + + r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o); + if (r < 0) + return r; + + FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) { + _cleanup_free_ char *item = NULL; + sd_id128_t id; + unsigned long long ll; + int k = 0; + + if (l < 2 || w[1] != '=') + return -EINVAL; + + item = strndup(w, l); + if (!item) + return -ENOMEM; + + switch (w[0]) { + + case 's': + k = sd_id128_from_string(item+2, &id); + if (k < 0) + return k; + if (!sd_id128_equal(id, j->current_file->header->seqnum_id)) + return 0; + break; + + case 'i': + if (sscanf(item+2, "%llx", &ll) != 1) + return -EINVAL; + if (ll != le64toh(o->entry.seqnum)) + return 0; + break; + + case 'b': + k = sd_id128_from_string(item+2, &id); + if (k < 0) + return k; + if (!sd_id128_equal(id, o->entry.boot_id)) + return 0; + break; + + case 'm': + if (sscanf(item+2, "%llx", &ll) != 1) + return -EINVAL; + if (ll != le64toh(o->entry.monotonic)) + return 0; + break; + + case 't': + if (sscanf(item+2, "%llx", &ll) != 1) + return -EINVAL; + if (ll != le64toh(o->entry.realtime)) + return 0; + break; + + case 'x': + if (sscanf(item+2, "%llx", &ll) != 1) + return -EINVAL; + if (ll != le64toh(o->entry.xor_hash)) + return 0; + break; + } + } + + return 1; +} + + _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) { - if (!j) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); reset_location(j); - j->current_location.type = LOCATION_DISCRETE; + j->current_location.type = LOCATION_SEEK; j->current_location.boot_id = boot_id; j->current_location.monotonic = usec; j->current_location.monotonic_set = true; @@ -898,11 +1197,11 @@ _public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, u } _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) { - if (!j) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); reset_location(j); - j->current_location.type = LOCATION_DISCRETE; + j->current_location.type = LOCATION_SEEK; j->current_location.realtime = usec; j->current_location.realtime_set = true; @@ -910,8 +1209,8 @@ _public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) { } _public_ int sd_journal_seek_head(sd_journal *j) { - if (!j) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); reset_location(j); j->current_location.type = LOCATION_HEAD; @@ -920,8 +1219,8 @@ _public_ int sd_journal_seek_head(sd_journal *j) { } _public_ int sd_journal_seek_tail(sd_journal *j) { - if (!j) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); reset_location(j); j->current_location.type = LOCATION_TAIL; @@ -929,48 +1228,80 @@ _public_ int sd_journal_seek_tail(sd_journal *j) { return 0; } -static int add_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) { - char *fn; - int r; - JournalFile *f; +static void check_network(sd_journal *j, int fd) { + struct statfs sfs; assert(j); - assert(prefix); - assert(filename); - if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) && - !startswith(filename, "system.journal")) - return 0; + if (j->on_network) + return; - if (dir) - fn = join(prefix, "/", dir, "/", filename, NULL); - else - fn = join(prefix, "/", filename, NULL); + if (fstatfs(fd, &sfs) < 0) + return; - if (!fn) - return -ENOMEM; + j->on_network = + F_TYPE_EQUAL(sfs.f_type, CIFS_MAGIC_NUMBER) || + F_TYPE_EQUAL(sfs.f_type, CODA_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, NCP_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, NFS_SUPER_MAGIC) || + F_TYPE_EQUAL(sfs.f_type, SMB_SUPER_MAGIC); +} - if (hashmap_get(j->files, fn)) { - free(fn); - return 0; - } +static bool file_has_type_prefix(const char *prefix, const char *filename) { + const char *full, *tilded, *atted; - if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) { - log_debug("Too many open journal files, not adding %s, ignoring.", fn); - free(fn); - return 0; + full = strappenda(prefix, ".journal"); + tilded = strappenda(full, "~"); + atted = strappenda(prefix, "@"); + + return streq(filename, full) || + streq(filename, tilded) || + startswith(filename, atted); +} + +static bool file_type_wanted(int flags, const char *filename) { + if (!endswith(filename, ".journal") && !endswith(filename, ".journal~")) + return false; + + /* no flags set → every type is OK */ + if (!(flags & (SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER))) + return true; + + if (flags & SD_JOURNAL_SYSTEM && file_has_type_prefix("system", filename)) + return true; + + if (flags & SD_JOURNAL_CURRENT_USER) { + char prefix[5 + DECIMAL_STR_MAX(uid_t) + 1]; + + assert_se(snprintf(prefix, sizeof(prefix), "user-%lu", (unsigned long) getuid()) + < (int) sizeof(prefix)); + + if (file_has_type_prefix(prefix, filename)) + return true; } - r = journal_file_open(fn, O_RDONLY, 0, NULL, &f); - free(fn); + return false; +} - if (r < 0) { - if (errno == ENOENT) - return 0; +static int add_any_file(sd_journal *j, const char *path) { + JournalFile *f = NULL; + int r; - return r; + assert(j); + assert(path); + + if (hashmap_get(j->files, path)) + return 0; + + if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) { + log_warning("Too many open journal files, not adding %s.", path); + return set_put_error(j, -ETOOMANYREFS); } + r = journal_file_open(path, O_RDONLY, 0, false, false, NULL, j->mmap, NULL, &f); + if (r < 0) + return r; + /* journal_file_dump(f); */ r = hashmap_put(j->files, f->path, f); @@ -979,246 +1310,414 @@ static int add_file(sd_journal *j, const char *prefix, const char *dir, const ch return r; } - log_debug("File %s got added.", f->path); + log_debug("File %s added.", f->path); + + check_network(j, f->fd); + + j->current_invalidate_counter ++; return 0; } -static int remove_file(sd_journal *j, const char *prefix, const char *dir, const char *filename) { - char *fn; - JournalFile *f; +static int add_file(sd_journal *j, const char *prefix, const char *filename) { + _cleanup_free_ char *path = NULL; + int r; assert(j); assert(prefix); assert(filename); - if (dir) - fn = join(prefix, "/", dir, "/", filename, NULL); - else - fn = join(prefix, "/", filename, NULL); + if (j->no_new_files || + !file_type_wanted(j->flags, filename)) + return 0; - if (!fn) + path = strjoin(prefix, "/", filename, NULL); + if (!path) return -ENOMEM; - f = hashmap_get(j->files, fn); - free(fn); + r = add_any_file(j, path); + if (r == -ENOENT) + return 0; + return 0; +} + +static int remove_file(sd_journal *j, const char *prefix, const char *filename) { + _cleanup_free_ char *path; + JournalFile *f; + + assert(j); + assert(prefix); + assert(filename); + + path = strjoin(prefix, "/", filename, NULL); + if (!path) + return -ENOMEM; + f = hashmap_get(j->files, path); if (!f) return 0; + remove_file_real(j, f); + return 0; +} + +static void remove_file_real(sd_journal *j, JournalFile *f) { + assert(j); + assert(f); + hashmap_remove(j->files, f->path); + + log_debug("File %s removed.", f->path); + + if (j->current_file == f) { + j->current_file = NULL; + j->current_field = 0; + } + + if (j->unique_file == f) { + j->unique_file = NULL; + j->unique_offset = 0; + } + journal_file_close(f); - log_debug("File %s got removed.", f->path); - return 0; + j->current_invalidate_counter ++; } -static int add_directory(sd_journal *j, const char *prefix, const char *dir) { - char *fn; +static int add_directory(sd_journal *j, const char *prefix, const char *dirname) { + _cleanup_free_ char *path = NULL; int r; - DIR *d; - int wd; + _cleanup_closedir_ DIR *d = NULL; sd_id128_t id, mid; + Directory *m; assert(j); assert(prefix); - assert(dir); + assert(dirname); + + log_debug("Considering %s/%s.", prefix, dirname); if ((j->flags & SD_JOURNAL_LOCAL_ONLY) && - (sd_id128_from_string(dir, &id) < 0 || + (sd_id128_from_string(dirname, &id) < 0 || sd_id128_get_machine(&mid) < 0 || - !sd_id128_equal(id, mid))) + !(sd_id128_equal(id, mid) || path_startswith(prefix, "/run")))) return 0; - fn = join(prefix, "/", dir, NULL); - if (!fn) + path = strjoin(prefix, "/", dirname, NULL); + if (!path) return -ENOMEM; - d = opendir(fn); - + d = opendir(path); if (!d) { - free(fn); + log_debug("Failed to open %s: %m", path); if (errno == ENOENT) return 0; - return -errno; } - wd = inotify_add_watch(j->inotify_fd, fn, - IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| - IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT| - IN_DONT_FOLLOW|IN_ONLYDIR); - if (wd > 0) { - if (hashmap_put(j->inotify_wd_dirs, INT_TO_PTR(wd), fn) < 0) - inotify_rm_watch(j->inotify_fd, wd); - else - fn = NULL; - } + m = hashmap_get(j->directories_by_path, path); + if (!m) { + m = new0(Directory, 1); + if (!m) + return -ENOMEM; - free(fn); + m->is_root = false; + m->path = path; - for (;;) { - struct dirent buf, *de; + if (hashmap_put(j->directories_by_path, m->path, m) < 0) { + free(m); + return -ENOMEM; + } - r = readdir_r(d, &buf, &de); - if (r != 0 || !de) - break; + path = NULL; /* avoid freeing in cleanup */ + j->current_invalidate_counter ++; - if (!dirent_is_file_with_suffix(de, ".journal")) - continue; + log_debug("Directory %s added.", m->path); - r = add_file(j, prefix, dir, de->d_name); - if (r < 0) - log_debug("Failed to add file %s/%s/%s: %s", prefix, dir, de->d_name, strerror(-r)); + } else if (m->is_root) + return 0; + + if (m->wd <= 0 && j->inotify_fd >= 0) { + + m->wd = inotify_add_watch(j->inotify_fd, m->path, + IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| + IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|IN_MOVED_FROM| + IN_ONLYDIR); + + if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0) + inotify_rm_watch(j->inotify_fd, m->wd); } - closedir(d); + for (;;) { + struct dirent *de; + + errno = 0; + de = readdir(d); + if (!de && errno != 0) { + r = -errno; + log_debug("Failed to read directory %s: %s", + m->path, strerror(errno)); + return r; + } + if (!de) + break; + + if (dirent_is_file_with_suffix(de, ".journal") || + dirent_is_file_with_suffix(de, ".journal~")) { + r = add_file(j, m->path, de->d_name); + if (r < 0) { + log_debug("Failed to add file %s/%s: %s", + m->path, de->d_name, strerror(-r)); + r = set_put_error(j, r); + if (r < 0) + return r; + } + } + } - log_debug("Directory %s/%s got added.", prefix, dir); + check_network(j, dirfd(d)); return 0; } -static void remove_directory_wd(sd_journal *j, int wd) { - char *p; +static int add_root_directory(sd_journal *j, const char *p) { + _cleanup_closedir_ DIR *d = NULL; + Directory *m; + int r; assert(j); - assert(wd > 0); + assert(p); - if (j->inotify_fd >= 0) - inotify_rm_watch(j->inotify_fd, wd); + if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) && + !path_startswith(p, "/run")) + return -EINVAL; - p = hashmap_remove(j->inotify_wd_dirs, INT_TO_PTR(wd)); + if (j->prefix) + p = strappenda(j->prefix, p); - if (p) { - log_debug("Directory %s got removed.", p); - free(p); - } -} + d = opendir(p); + if (!d) + return -errno; -static void add_root_wd(sd_journal *j, const char *p) { - int wd; - char *k; + m = hashmap_get(j->directories_by_path, p); + if (!m) { + m = new0(Directory, 1); + if (!m) + return -ENOMEM; - assert(j); - assert(p); + m->is_root = true; + m->path = strdup(p); + if (!m->path) { + free(m); + return -ENOMEM; + } - wd = inotify_add_watch(j->inotify_fd, p, - IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| - IN_DONT_FOLLOW|IN_ONLYDIR); - if (wd <= 0) - return; + if (hashmap_put(j->directories_by_path, m->path, m) < 0) { + free(m->path); + free(m); + return -ENOMEM; + } - k = strdup(p); - if (!k || hashmap_put(j->inotify_wd_roots, INT_TO_PTR(wd), k) < 0) { - inotify_rm_watch(j->inotify_fd, wd); - free(k); - } -} + j->current_invalidate_counter ++; -static void remove_root_wd(sd_journal *j, int wd) { - char *p; + log_debug("Root directory %s added.", m->path); - assert(j); - assert(wd > 0); + } else if (!m->is_root) + return 0; - if (j->inotify_fd >= 0) - inotify_rm_watch(j->inotify_fd, wd); + if (m->wd <= 0 && j->inotify_fd >= 0) { - p = hashmap_remove(j->inotify_wd_roots, INT_TO_PTR(wd)); + m->wd = inotify_add_watch(j->inotify_fd, m->path, + IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE| + IN_ONLYDIR); - if (p) { - log_debug("Root %s got removed.", p); - free(p); + if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0) + inotify_rm_watch(j->inotify_fd, m->wd); } -} -_public_ int sd_journal_open(sd_journal **ret, int flags) { - sd_journal *j; - const char *p; - const char search_paths[] = - "/run/log/journal\0" - "/var/log/journal\0"; - int r; + if (j->no_new_files) + return 0; - if (!ret) - return -EINVAL; + for (;;) { + struct dirent *de; + sd_id128_t id; - if (flags & ~(SD_JOURNAL_LOCAL_ONLY| - SD_JOURNAL_RUNTIME_ONLY| - SD_JOURNAL_SYSTEM_ONLY)) - return -EINVAL; - - j = new0(sd_journal, 1); - if (!j) - return -ENOMEM; + errno = 0; + de = readdir(d); + if (!de && errno != 0) { + r = -errno; + log_debug("Failed to read directory %s: %s", + m->path, strerror(errno)); + return r; + } + if (!de) + break; - j->flags = flags; + if (dirent_is_file_with_suffix(de, ".journal") || + dirent_is_file_with_suffix(de, ".journal~")) { + r = add_file(j, m->path, de->d_name); + if (r < 0) { + log_debug("Failed to add file %s/%s: %s", + m->path, de->d_name, strerror(-r)); + r = set_put_error(j, r); + if (r < 0) + return r; + } + } else if ((de->d_type == DT_DIR || de->d_type == DT_LNK || de->d_type == DT_UNKNOWN) && + sd_id128_from_string(de->d_name, &id) >= 0) { - j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (j->inotify_fd < 0) { - r = -errno; - goto fail; + r = add_directory(j, m->path, de->d_name); + if (r < 0) + log_debug("Failed to add directory %s/%s: %s", m->path, de->d_name, strerror(-r)); + } } - j->files = hashmap_new(string_hash_func, string_compare_func); - if (!j->files) { - r = -ENOMEM; - goto fail; - } + check_network(j, dirfd(d)); + + return 0; +} + +static int remove_directory(sd_journal *j, Directory *d) { + assert(j); - j->inotify_wd_dirs = hashmap_new(trivial_hash_func, trivial_compare_func); - j->inotify_wd_roots = hashmap_new(trivial_hash_func, trivial_compare_func); + if (d->wd > 0) { + hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd)); - if (!j->inotify_wd_dirs || !j->inotify_wd_roots) { - r = -ENOMEM; - goto fail; + if (j->inotify_fd >= 0) + inotify_rm_watch(j->inotify_fd, d->wd); } + hashmap_remove(j->directories_by_path, d->path); + + if (d->is_root) + log_debug("Root directory %s removed.", d->path); + else + log_debug("Directory %s removed.", d->path); + + free(d->path); + free(d); + + return 0; +} + +static int add_search_paths(sd_journal *j) { + int r; + const char search_paths[] = + "/run/log/journal\0" + "/var/log/journal\0"; + const char *p; + + assert(j); + /* We ignore most errors here, since the idea is to only open * what's actually accessible, and ignore the rest. */ NULSTR_FOREACH(p, search_paths) { - DIR *d; + r = add_root_directory(j, p); + if (r < 0 && r != -ENOENT) { + r = set_put_error(j, r); + if (r < 0) + return r; + } + } - if ((flags & SD_JOURNAL_RUNTIME_ONLY) && - !path_startswith(p, "/run")) - continue; + return 0; +} - d = opendir(p); - if (!d) { - if (errno != ENOENT) - log_debug("Failed to open %s: %m", p); - continue; +static int add_current_paths(sd_journal *j) { + Iterator i; + JournalFile *f; + + assert(j); + assert(j->no_new_files); + + /* Simply adds all directories for files we have open as + * "root" directories. We don't expect errors here, so we + * treat them as fatal. */ + + HASHMAP_FOREACH(f, j->files, i) { + _cleanup_free_ char *dir; + int r; + + dir = dirname_malloc(f->path); + if (!dir) + return -ENOMEM; + + r = add_root_directory(j, dir); + if (r < 0) { + set_put_error(j, r); + return r; } + } - add_root_wd(j, p); + return 0; +} - for (;;) { - struct dirent buf, *de; - sd_id128_t id; - r = readdir_r(d, &buf, &de); - if (r != 0 || !de) - break; +static int allocate_inotify(sd_journal *j) { + assert(j); - if (dirent_is_file_with_suffix(de, ".journal")) { - r = add_file(j, p, NULL, de->d_name); - if (r < 0) - log_debug("Failed to add file %s/%s: %s", p, de->d_name, strerror(-r)); + if (j->inotify_fd < 0) { + j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (j->inotify_fd < 0) + return -errno; + } - } else if ((de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) && - sd_id128_from_string(de->d_name, &id) >= 0) { + if (!j->directories_by_wd) { + j->directories_by_wd = hashmap_new(trivial_hash_func, trivial_compare_func); + if (!j->directories_by_wd) + return -ENOMEM; + } - r = add_directory(j, p, de->d_name); - if (r < 0) - log_debug("Failed to add directory %s/%s: %s", p, de->d_name, strerror(-r)); - } - } + return 0; +} + +static sd_journal *journal_new(int flags, const char *path) { + sd_journal *j; + + j = new0(sd_journal, 1); + if (!j) + return NULL; - closedir(d); + j->original_pid = getpid(); + j->inotify_fd = -1; + j->flags = flags; + j->data_threshold = DEFAULT_DATA_THRESHOLD; + + if (path) { + j->path = strdup(path); + if (!j->path) + goto fail; } + j->files = hashmap_new(string_hash_func, string_compare_func); + j->directories_by_path = hashmap_new(string_hash_func, string_compare_func); + j->mmap = mmap_cache_new(); + if (!j->files || !j->directories_by_path || !j->mmap) + goto fail; + + return j; + +fail: + sd_journal_close(j); + return NULL; +} + +_public_ int sd_journal_open(sd_journal **ret, int flags) { + sd_journal *j; + int r; + + assert_return(ret, -EINVAL); + assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_RUNTIME_ONLY|SD_JOURNAL_SYSTEM|SD_JOURNAL_CURRENT_USER)) == 0, -EINVAL); + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; + + r = add_search_paths(j); + if (r < 0) + goto fail; + *ret = j; return 0; @@ -1226,44 +1725,142 @@ fail: sd_journal_close(j); return r; -}; +} -_public_ void sd_journal_close(sd_journal *j) { +_public_ int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) { + _cleanup_free_ char *root = NULL, *class = NULL; + sd_journal *j; + char *p; + int r; + + assert_return(machine, -EINVAL); + assert_return(ret, -EINVAL); + assert_return((flags & ~(SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM)) == 0, -EINVAL); + assert_return(filename_is_safe(machine), -EINVAL); + + p = strappenda("/run/systemd/machines/", machine); + r = parse_env_file(p, NEWLINE, "ROOT", &root, "CLASS", &class, NULL); + if (r == -ENOENT) + return -EHOSTDOWN; + if (r < 0) + return r; + if (!root) + return -ENODATA; + + if (!streq_ptr(class, "container")) + return -EIO; + + j = journal_new(flags, NULL); if (!j) - return; + return -ENOMEM; - if (j->inotify_wd_dirs) { - void *k; + j->prefix = root; + root = NULL; + + r = add_search_paths(j); + if (r < 0) + goto fail; - while ((k = hashmap_first_key(j->inotify_wd_dirs))) - remove_directory_wd(j, PTR_TO_INT(k)); + *ret = j; + return 0; - hashmap_free(j->inotify_wd_dirs); - } +fail: + sd_journal_close(j); + return r; +} - if (j->inotify_wd_roots) { - void *k; +_public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) { + sd_journal *j; + int r; - while ((k = hashmap_first_key(j->inotify_wd_roots))) - remove_root_wd(j, PTR_TO_INT(k)); + assert_return(ret, -EINVAL); + assert_return(path, -EINVAL); + assert_return(flags == 0, -EINVAL); + + j = journal_new(flags, path); + if (!j) + return -ENOMEM; - hashmap_free(j->inotify_wd_roots); + r = add_root_directory(j, path); + if (r < 0) { + set_put_error(j, r); + goto fail; } - if (j->files) { - JournalFile *f; + *ret = j; + return 0; + +fail: + sd_journal_close(j); + + return r; +} + +_public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int flags) { + sd_journal *j; + const char **path; + int r; - while ((f = hashmap_steal_first(j->files))) - journal_file_close(f); + assert_return(ret, -EINVAL); + assert_return(flags == 0, -EINVAL); + + j = journal_new(flags, NULL); + if (!j) + return -ENOMEM; - hashmap_free(j->files); + STRV_FOREACH(path, paths) { + r = add_any_file(j, *path); + if (r < 0) { + log_error("Failed to open %s: %s", *path, strerror(-r)); + goto fail; + } } + j->no_new_files = true; + + *ret = j; + return 0; + +fail: + sd_journal_close(j); + + return r; +} + +_public_ void sd_journal_close(sd_journal *j) { + Directory *d; + JournalFile *f; + + if (!j) + return; + sd_journal_flush_matches(j); - if (j->inotify_fd >= 0) - close_nointr_nofail(j->inotify_fd); + while ((f = hashmap_steal_first(j->files))) + journal_file_close(f); + + hashmap_free(j->files); + + while ((d = hashmap_first(j->directories_by_path))) + remove_directory(j, d); + + while ((d = hashmap_first(j->directories_by_wd))) + remove_directory(j, d); + + hashmap_free(j->directories_by_path); + hashmap_free(j->directories_by_wd); + safe_close(j->inotify_fd); + + if (j->mmap) { + log_debug("mmap cache statistics: %u hit, %u miss", mmap_cache_get_hit(j->mmap), mmap_cache_get_missed(j->mmap)); + mmap_cache_unref(j->mmap); + } + + free(j->path); + free(j->prefix); + free(j->unique_field); + set_free(j->errors); free(j); } @@ -1272,10 +1869,9 @@ _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) { JournalFile *f; int r; - if (!j) - return -EINVAL; - if (!ret) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(ret, -EINVAL); f = j->current_file; if (!f) @@ -1298,10 +1894,8 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12 int r; sd_id128_t id; - if (!j) - return -EINVAL; - if (!ret) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); f = j->current_file; if (!f) @@ -1325,10 +1919,40 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12 return -ESTALE; } - *ret = le64toh(o->entry.monotonic); + if (ret) + *ret = le64toh(o->entry.monotonic); + return 0; } +static bool field_is_valid(const char *field) { + const char *p; + + assert(field); + + if (isempty(field)) + return false; + + if (startswith(field, "__")) + return false; + + for (p = field; *p; p++) { + + if (*p == '_') + continue; + + if (*p >= 'A' && *p <= 'Z') + continue; + + if (*p >= '0' && *p <= '9') + continue; + + return false; + } + + return true; +} + _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) { JournalFile *f; uint64_t i, n; @@ -1336,17 +1960,12 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** int r; Object *o; - if (!j) - return -EINVAL; - if (!field) - return -EINVAL; - if (!data) - return -EINVAL; - if (!size) - return -EINVAL; - - if (isempty(field) || strchr(field, '=')) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(field, -EINVAL); + assert_return(data, -EINVAL); + assert_return(size, -EINVAL); + assert_return(field_is_valid(field), -EINVAL); f = j->current_file; if (!f) @@ -1388,7 +2007,8 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** uint64_t rsize; if (!uncompress_blob(o->data.payload, l, - &f->compress_buffer, &f->compress_buffer_size, &rsize)) + &f->compress_buffer, &f->compress_buffer_size, &rsize, + j->data_threshold)) return -EBADMSG; *data = f->compress_buffer; @@ -1423,20 +2043,48 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** return -ENOENT; } +static int return_data(sd_journal *j, JournalFile *f, Object *o, const void **data, size_t *size) { + size_t t; + uint64_t l; + + l = le64toh(o->object.size) - offsetof(Object, data.payload); + t = (size_t) l; + + /* We can't read objects larger than 4G on a 32bit machine */ + if ((uint64_t) t != l) + return -E2BIG; + + if (o->object.flags & OBJECT_COMPRESSED) { +#ifdef HAVE_XZ + uint64_t rsize; + + if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize, j->data_threshold)) + return -EBADMSG; + + *data = f->compress_buffer; + *size = (size_t) rsize; +#else + return -EPROTONOSUPPORT; +#endif + } else { + *data = o->data.payload; + *size = t; + } + + return 0; +} + _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) { JournalFile *f; - uint64_t p, l, n; + uint64_t p, n; le64_t le_hash; int r; Object *o; - size_t t; - if (!j) - return -EINVAL; - if (!data) - return -EINVAL; - if (!size) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(data, -EINVAL); + assert_return(size, -EINVAL); f = j->current_file; if (!f) @@ -1462,115 +2110,137 @@ _public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t if (le_hash != o->data.hash) return -EBADMSG; - l = le64toh(o->object.size) - offsetof(Object, data.payload); - t = (size_t) l; + r = return_data(j, f, o, data, size); + if (r < 0) + return r; - /* We can't read objects larger than 4G on a 32bit machine */ - if ((uint64_t) t != l) - return -E2BIG; + j->current_field ++; - if (o->object.flags & OBJECT_COMPRESSED) { -#ifdef HAVE_XZ - uint64_t rsize; + return 1; +} - if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize)) - return -EBADMSG; +_public_ void sd_journal_restart_data(sd_journal *j) { + if (!j) + return; - *data = f->compress_buffer; - *size = (size_t) rsize; -#else - return -EPROTONOSUPPORT; -#endif - } else { - *data = o->data.payload; - *size = t; - } + j->current_field = 0; +} - j->current_field ++; +_public_ int sd_journal_get_fd(sd_journal *j) { + int r; - return 1; -} + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); -_public_ void sd_journal_restart_data(sd_journal *j) { - if (!j) - return; + if (j->inotify_fd >= 0) + return j->inotify_fd; - j->current_field = 0; -} + r = allocate_inotify(j); + if (r < 0) + return r; -_public_ int sd_journal_get_fd(sd_journal *j) { - if (!j) - return -EINVAL; + /* Iterate through all dirs again, to add them to the + * inotify */ + if (j->no_new_files) + r = add_current_paths(j); + else if (j->path) + r = add_root_directory(j, j->path); + else + r = add_search_paths(j); + if (r < 0) + return r; return j->inotify_fd; } -static void process_inotify_event(sd_journal *j, struct inotify_event *e) { - char *p; - int r; +_public_ int sd_journal_get_events(sd_journal *j) { + int fd; - assert(j); - assert(e); + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); - /* Is this a subdirectory we watch? */ - p = hashmap_get(j->inotify_wd_dirs, INT_TO_PTR(e->wd)); - if (p) { + fd = sd_journal_get_fd(j); + if (fd < 0) + return fd; - if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) { + return POLLIN; +} - /* Event for a journal file */ +_public_ int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec) { + int fd; - if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) { - r = add_file(j, p, NULL, e->name); - if (r < 0) - log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r)); - } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(timeout_usec, -EINVAL); - r = remove_file(j, p, NULL, e->name); - if (r < 0) - log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r)); - } + fd = sd_journal_get_fd(j); + if (fd < 0) + return fd; - } else if (e->len == 0) { + if (!j->on_network) { + *timeout_usec = (uint64_t) -1; + return 0; + } - /* Event for the directory itself */ + /* If we are on the network we need to regularly check for + * changes manually */ - if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) - remove_directory_wd(j, e->wd); - } + *timeout_usec = j->last_process_usec + JOURNAL_FILES_RECHECK_USEC; + return 1; +} - return; - } +static void process_inotify_event(sd_journal *j, struct inotify_event *e) { + Directory *d; + int r; - /* Must be the root directory then? */ - p = hashmap_get(j->inotify_wd_roots, INT_TO_PTR(e->wd)); - if (p) { + assert(j); + assert(e); + + /* Is this a subdirectory we watch? */ + d = hashmap_get(j->directories_by_wd, INT_TO_PTR(e->wd)); + if (d) { sd_id128_t id; - if (!(e->mask & IN_ISDIR) && e->len > 0 && endswith(e->name, ".journal")) { + if (!(e->mask & IN_ISDIR) && e->len > 0 && + (endswith(e->name, ".journal") || + endswith(e->name, ".journal~"))) { /* Event for a journal file */ if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) { - r = add_file(j, p, NULL, e->name); + r = add_file(j, d->path, e->name); + if (r < 0) { + log_debug("Failed to add file %s/%s: %s", + d->path, e->name, strerror(-r)); + set_put_error(j, r); + } + + } else if (e->mask & (IN_DELETE|IN_MOVED_FROM|IN_UNMOUNT)) { + + r = remove_file(j, d->path, e->name); if (r < 0) - log_debug("Failed to add file %s/%s: %s", p, e->name, strerror(-r)); - } else if (e->mask & (IN_DELETE|IN_UNMOUNT)) { + log_debug("Failed to remove file %s/%s: %s", d->path, e->name, strerror(-r)); + } + + } else if (!d->is_root && e->len == 0) { + + /* Event for a subdirectory */ - r = remove_file(j, p, NULL, e->name); + if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) { + r = remove_directory(j, d); if (r < 0) - log_debug("Failed to remove file %s/%s: %s", p, e->name, strerror(-r)); + log_debug("Failed to remove directory %s: %s", d->path, strerror(-r)); } - } else if ((e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) { - /* Event for subdirectory */ + } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) { - if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) { + /* Event for root directory */ - r = add_directory(j, p, e->name); + if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) { + r = add_directory(j, d->path, e->name); if (r < 0) - log_debug("Failed to add directory %s/%s: %s", p, e->name, strerror(-r)); + log_debug("Failed to add directory %s/%s: %s", d->path, e->name, strerror(-r)); } } @@ -1583,11 +2253,25 @@ static void process_inotify_event(sd_journal *j, struct inotify_event *e) { log_warning("Unknown inotify event."); } +static int determine_change(sd_journal *j) { + bool b; + + assert(j); + + b = j->current_invalidate_counter != j->last_invalidate_counter; + j->last_invalidate_counter = j->current_invalidate_counter; + + return b ? SD_JOURNAL_INVALIDATE : SD_JOURNAL_APPEND; +} + _public_ int sd_journal_process(sd_journal *j) { - uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX]; + uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX] _alignas_(struct inotify_event); + bool got_something = false; - if (!j) - return -EINVAL; + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + j->last_process_usec = now(CLOCK_MONOTONIC); for (;;) { struct inotify_event *e; @@ -1595,12 +2279,14 @@ _public_ int sd_journal_process(sd_journal *j) { l = read(j->inotify_fd, buffer, sizeof(buffer)); if (l < 0) { - if (errno == EINTR || errno == EAGAIN) - return 0; + if (errno == EAGAIN || errno == EINTR) + return got_something ? determine_change(j) : SD_JOURNAL_NOP; return -errno; } + got_something = true; + e = (struct inotify_event*) buffer; while (l > 0) { size_t step; @@ -1616,27 +2302,397 @@ _public_ int sd_journal_process(sd_journal *j) { } } -/* _public_ int sd_journal_query_unique(sd_journal *j, const char *field) { */ -/* if (!j) */ -/* return -EINVAL; */ -/* if (!field) */ -/* return -EINVAL; */ +_public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { + int r; + uint64_t t; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + if (j->inotify_fd < 0) { + + /* This is the first invocation, hence create the + * inotify watch */ + r = sd_journal_get_fd(j); + if (r < 0) + return r; + + /* The journal might have changed since the context + * object was created and we weren't watching before, + * hence don't wait for anything, and return + * immediately. */ + return determine_change(j); + } + + r = sd_journal_get_timeout(j, &t); + if (r < 0) + return r; + + if (t != (uint64_t) -1) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + t = t > n ? t - n : 0; + + if (timeout_usec == (uint64_t) -1 || timeout_usec > t) + timeout_usec = t; + } + + do { + r = fd_wait_for_event(j->inotify_fd, POLLIN, timeout_usec); + } while (r == -EINTR); + + if (r < 0) + return r; + + return sd_journal_process(j); +} + +_public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) { + Iterator i; + JournalFile *f; + bool first = true; + uint64_t fmin = 0, tmax = 0; + int r; -/* return -ENOTSUP; */ -/* } */ + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(from || to, -EINVAL); + assert_return(from != to, -EINVAL); -/* _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { */ -/* if (!j) */ -/* return -EINVAL; */ -/* if (!data) */ -/* return -EINVAL; */ -/* if (!l) */ -/* return -EINVAL; */ + HASHMAP_FOREACH(f, j->files, i) { + usec_t fr, t; -/* return -ENOTSUP; */ -/* } */ + r = journal_file_get_cutoff_realtime_usec(f, &fr, &t); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + if (r == 0) + continue; + + if (first) { + fmin = fr; + tmax = t; + first = false; + } else { + fmin = MIN(fr, fmin); + tmax = MAX(t, tmax); + } + } + + if (from) + *from = fmin; + if (to) + *to = tmax; + + return first ? 0 : 1; +} + +_public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) { + Iterator i; + JournalFile *f; + bool first = true; + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(from || to, -EINVAL); + assert_return(from != to, -EINVAL); + + HASHMAP_FOREACH(f, j->files, i) { + usec_t fr, t; + + r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + if (r == 0) + continue; + + if (first) { + if (from) + *from = fr; + if (to) + *to = t; + first = false; + } else { + if (from) + *from = MIN(fr, *from); + if (to) + *to = MAX(t, *to); + } + } + + return first ? 0 : 1; +} + +void journal_print_header(sd_journal *j) { + Iterator i; + JournalFile *f; + bool newline = false; + + assert(j); -/* _public_ void sd_journal_restart_unique(sd_journal *j) { */ -/* if (!j) */ -/* return; */ -/* } */ + HASHMAP_FOREACH(f, j->files, i) { + if (newline) + putchar('\n'); + else + newline = true; + + journal_file_print_header(f); + } +} + +_public_ int sd_journal_get_usage(sd_journal *j, uint64_t *bytes) { + Iterator i; + JournalFile *f; + uint64_t sum = 0; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(bytes, -EINVAL); + + HASHMAP_FOREACH(f, j->files, i) { + struct stat st; + + if (fstat(f->fd, &st) < 0) + return -errno; + + sum += (uint64_t) st.st_blocks * 512ULL; + } + + *bytes = sum; + return 0; +} + +_public_ int sd_journal_query_unique(sd_journal *j, const char *field) { + char *f; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(!isempty(field), -EINVAL); + assert_return(field_is_valid(field), -EINVAL); + + f = strdup(field); + if (!f) + return -ENOMEM; + + free(j->unique_field); + j->unique_field = f; + j->unique_file = NULL; + j->unique_offset = 0; + + return 0; +} + +_public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) { + size_t k; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(data, -EINVAL); + assert_return(l, -EINVAL); + assert_return(j->unique_field, -EINVAL); + + k = strlen(j->unique_field); + + if (!j->unique_file) { + j->unique_file = hashmap_first(j->files); + if (!j->unique_file) + return 0; + j->unique_offset = 0; + } + + for (;;) { + JournalFile *of; + Iterator i; + Object *o; + const void *odata; + size_t ol; + bool found; + int r; + + /* Proceed to next data object in the field's linked list */ + if (j->unique_offset == 0) { + r = journal_file_find_field_object(j->unique_file, j->unique_field, k, &o, NULL); + if (r < 0) + return r; + + j->unique_offset = r > 0 ? le64toh(o->field.head_data_offset) : 0; + } else { + r = journal_file_move_to_object(j->unique_file, OBJECT_DATA, j->unique_offset, &o); + if (r < 0) + return r; + + j->unique_offset = le64toh(o->data.next_field_offset); + } + + /* We reached the end of the list? Then start again, with the next file */ + if (j->unique_offset == 0) { + JournalFile *n; + + n = hashmap_next(j->files, j->unique_file->path); + if (!n) + return 0; + + j->unique_file = n; + continue; + } + + /* We do not use the type context here, but 0 instead, + * so that we can look at this data object at the same + * time as one on another file */ + r = journal_file_move_to_object(j->unique_file, 0, j->unique_offset, &o); + if (r < 0) + return r; + + /* Let's do the type check by hand, since we used 0 context above. */ + if (o->object.type != OBJECT_DATA) { + log_error("%s:offset " OFSfmt ": object has type %d, expected %d", + j->unique_file->path, j->unique_offset, + o->object.type, OBJECT_DATA); + return -EBADMSG; + } + + r = journal_file_object_keep(j->unique_file, o, j->unique_offset); + if (r < 0) + return r; + + r = return_data(j, j->unique_file, o, &odata, &ol); + if (r < 0) + return r; + + /* OK, now let's see if we already returned this data + * object by checking if it exists in the earlier + * traversed files. */ + found = false; + HASHMAP_FOREACH(of, j->files, i) { + Object *oo; + uint64_t op; + + if (of == j->unique_file) + break; + + /* Skip this file it didn't have any fields + * indexed */ + if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && + le64toh(of->header->n_fields) <= 0) + continue; + + r = journal_file_find_data_object_with_hash(of, odata, ol, le64toh(o->data.hash), &oo, &op); + if (r < 0) + return r; + + if (r > 0) + found = true; + } + + if (found) + continue; + + r = journal_file_object_release(j->unique_file, o, j->unique_offset); + if (r < 0) + return r; + + r = return_data(j, j->unique_file, o, data, l); + if (r < 0) + return r; + + return 1; + } +} + +_public_ void sd_journal_restart_unique(sd_journal *j) { + if (!j) + return; + + j->unique_file = NULL; + j->unique_offset = 0; +} + +_public_ int sd_journal_reliable_fd(sd_journal *j) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + return !j->on_network; +} + +static char *lookup_field(const char *field, void *userdata) { + sd_journal *j = userdata; + const void *data; + size_t size, d; + int r; + + assert(field); + assert(j); + + r = sd_journal_get_data(j, field, &data, &size); + if (r < 0 || + size > REPLACE_VAR_MAX) + return strdup(field); + + d = strlen(field) + 1; + + return strndup((const char*) data + d, size - d); +} + +_public_ int sd_journal_get_catalog(sd_journal *j, char **ret) { + const void *data; + size_t size; + sd_id128_t id; + _cleanup_free_ char *text = NULL, *cid = NULL; + char *t; + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(ret, -EINVAL); + + r = sd_journal_get_data(j, "MESSAGE_ID", &data, &size); + if (r < 0) + return r; + + cid = strndup((const char*) data + 11, size - 11); + if (!cid) + return -ENOMEM; + + r = sd_id128_from_string(cid, &id); + if (r < 0) + return r; + + r = catalog_get(CATALOG_DATABASE, id, &text); + if (r < 0) + return r; + + t = replace_var(text, lookup_field, j); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +_public_ int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **ret) { + assert_return(ret, -EINVAL); + + return catalog_get(CATALOG_DATABASE, id, ret); +} + +_public_ int sd_journal_set_data_threshold(sd_journal *j, size_t sz) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + + j->data_threshold = sz; + return 0; +} + +_public_ int sd_journal_get_data_threshold(sd_journal *j, size_t *sz) { + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(sz, -EINVAL); + + *sz = j->data_threshold; + return 0; +}