+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ }
+
+ /* Otherwise compare UTC time */
+ a = le64toh(ao->entry.realtime);
+ b = le64toh(bo->entry.realtime);
+
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+
+ /* Finally, compare by contents */
+ a = le64toh(ao->entry.xor_hash);
+ b = le64toh(bo->entry.xor_hash);
+
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+
+ return 0;
+}
+
+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 || l->type == LOCATION_SEEK);
+
+ if (l->monotonic_set &&
+ sd_id128_equal(ao->entry.boot_id, l->boot_id) &&
+ l->realtime_set &&
+ le64toh(ao->entry.realtime) == l->realtime &&
+ l->xor_hash_set &&
+ le64toh(ao->entry.xor_hash) == l->xor_hash)
+ return 0;
+
+ if (l->seqnum_set &&
+ sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) {
+
+ a = le64toh(ao->entry.seqnum);
+
+ if (a < l->seqnum)
+ return -1;
+ if (a > l->seqnum)
+ return 1;
+ }
+
+ if (l->monotonic_set &&
+ sd_id128_equal(ao->entry.boot_id, l->boot_id)) {
+
+ a = le64toh(ao->entry.monotonic);
+
+ if (a < l->monotonic)
+ return -1;
+ if (a > l->monotonic)
+ return 1;
+ }
+
+ if (l->realtime_set) {
+
+ a = le64toh(ao->entry.realtime);
+
+ if (a < l->realtime)
+ return -1;
+ if (a > l->realtime)
+ return 1;
+ }
+
+ if (l->xor_hash_set) {
+ a = le64toh(ao->entry.xor_hash);
+
+ if (a < l->xor_hash)
+ return -1;
+ if (a > l->xor_hash)
+ return 1;
+ }
+
+ return 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 (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;
+
+ return journal_file_move_to_entry_by_offset_for_data(f, dp, after_offset, direction, ret, offset);
+
+ } else if (m->type == MATCH_OR_TERM) {
+ Match *i;
+
+ /* Find the earliest match beyond after_offset */
+
+ 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 ? np > cp : np < cp))
+ np = cp;
+ }
+ }
+
+ } else if (m->type == MATCH_AND_TERM) {
+ Match *i;
+ bool continue_looking;
+
+ /* Always jump to the next matching entry and repeat
+ * this until we fine and offset that matches for all
+ * matches. */
+
+ if (!m->matches)
+ return 0;
+
+ np = 0;
+ do {
+ continue_looking = false;
+
+ LIST_FOREACH(matches, i, m->matches) {
+ uint64_t cp, limit;
+
+ if (np == 0)
+ limit = after_offset;
+ else if (direction == DIRECTION_DOWN)
+ limit = MAX(np, after_offset);
+ else
+ limit = MIN(np, after_offset);
+
+ r = next_for_match(j, i, f, limit, direction, NULL, &cp);
+ if (r <= 0)
+ return r;
+
+ if ((direction == DIRECTION_DOWN ? cp >= after_offset : cp <= after_offset) &&
+ (np == 0 || (direction == DIRECTION_DOWN ? cp > np : np < cp))) {
+ np = cp;
+ continue_looking = true;
+ }
+ }
+
+ } while (continue_looking);
+ }
+
+ if (np == 0)
+ return 0;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = n;
+ if (offset)
+ *offset = np;
+
+ return 1;
+}
+
+static int find_location_for_match(
+ sd_journal *j,
+ Match *m,
+ JournalFile *f,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ int r;
+
+ assert(j);
+ assert(m);
+ assert(f);
+
+ 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;
+
+ /* FIXME: missing: find by monotonic */
+
+ 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);
+
+ return journal_file_next_entry_for_data(f, NULL, 0, dp, direction, ret, offset);
+
+ } else if (m->type == MATCH_OR_TERM) {
+ uint64_t np = 0;
+ Object *n;
+ Match *i;
+
+ /* Find the earliest match */
+
+ 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;
+ else if (r > 0) {
+ if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp))
+ np = cp;
+ }
+ }
+
+ if (np == 0)
+ return 0;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = n;
+ if (offset)
+ *offset = np;
+
+ 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;
+
+ 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 ? np < cp : np > cp))
+ np = cp;
+ }
+
+ return next_for_match(j, m, f, np, direction, ret, offset);
+ }
+}
+
+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 (!j->level0) {
+ /* No matches is simple */
+
+ 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);
+
+ return journal_file_next_entry(f, NULL, 0, direction, ret, offset);
+ } else
+ return find_location_for_match(j, j->level0, f, direction, ret, offset);
+}
+
+static int next_with_matches(
+ sd_journal *j,
+ JournalFile *f,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ 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->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 to 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;
+
+ if (j->current_location.type == LOCATION_DISCRETE) {
+ int k;
+
+ k = compare_with_location(f, c, &j->current_location);
+ if (direction == DIRECTION_DOWN)
+ found = k > 0;
+ else
+ found = k < 0;
+ } else
+ found = true;
+
+ if (found) {
+ if (ret)
+ *ret = c;
+ if (offset)
+ *offset = cp;
+ return 1;
+ }
+
+ r = next_with_matches(j, f, direction, &c, &cp);
+ if (r <= 0)
+ return r;
+ }
+}
+
+static int real_journal_next(sd_journal *j, direction_t direction) {
+ JournalFile *f, *new_file = NULL;
+ uint64_t new_offset = 0;
+ Object *o;
+ uint64_t p;
+ Iterator i;
+ int r;
+
+ if (!j)
+ return -EINVAL;
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ bool found;
+
+ r = next_beyond_location(j, f, direction, &o, &p);
+ if (r < 0) {
+ log_debug("Can't iterate through %s, ignoring: %s", f->path, strerror(-r));
+ continue;
+ } else if (r == 0)
+ continue;
+
+ if (!new_file)
+ found = true;
+ else {
+ int k;
+
+ k = compare_entry_order(f, o, new_file, new_offset);
+
+ if (direction == DIRECTION_DOWN)
+ found = k < 0;
+ else
+ found = k > 0;
+ }
+
+ if (found) {
+ new_file = f;
+ new_offset = p;
+ }
+ }
+
+ if (!new_file)
+ return 0;
+
+ 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, new_offset);
+
+ return 1;
+}
+
+_public_ int sd_journal_next(sd_journal *j) {
+ return real_journal_next(j, DIRECTION_DOWN);
+}
+
+_public_ int sd_journal_previous(sd_journal *j) {
+ return real_journal_next(j, DIRECTION_UP);
+}
+
+static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
+ int c = 0, r;
+
+ if (!j)
+ return -EINVAL;
+
+ if (skip == 0) {
+ /* If this is not a discrete skip, then at least
+ * resolve the current location */
+ if (j->current_location.type != LOCATION_DISCRETE)
+ return real_journal_next(j, direction);
+
+ return 0;
+ }
+
+ do {
+ r = real_journal_next(j, direction);
+ if (r < 0)
+ return r;
+
+ if (r == 0)
+ return c;
+
+ skip--;
+ c++;
+ } while (skip > 0);
+
+ return c;
+}
+
+_public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
+ return real_journal_next_skip(j, DIRECTION_DOWN, skip);
+}
+
+_public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
+ return real_journal_next_skip(j, DIRECTION_UP, skip);
+}
+
+_public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
+ Object *o;
+ int r;
+ char bid[33], sid[33];
+
+ if (!j)
+ return -EINVAL;
+ if (!cursor)
+ return -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;
+
+ sd_id128_to_string(j->current_file->header->seqnum_id, sid);
+ sd_id128_to_string(o->entry.boot_id, bid);
+
+ if (asprintf(cursor,
+ "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx",
+ 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)) < 0)
+ return -ENOMEM;
+
+ return 1;
+}
+
+_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
+ char *w, *state;
+ size_t l;
+ unsigned long long seqnum, monotonic, realtime, xor_hash;
+ bool
+ seqnum_id_set = false,
+ seqnum_set = false,
+ boot_id_set = false,
+ monotonic_set = false,
+ realtime_set = false,
+ xor_hash_set = false;
+ sd_id128_t seqnum_id, boot_id;
+
+ if (!j)
+ return -EINVAL;
+ if (isempty(cursor))
+ return -EINVAL;
+
+ FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
+ char *item;
+ int k = 0;
+
+ if (l < 2 || w[1] != '=')
+ return -EINVAL;
+
+ item = strndup(w, l);
+ if (!item)
+ return -ENOMEM;
+
+ switch (w[0]) {
+
+ case 's':
+ seqnum_id_set = true;
+ k = sd_id128_from_string(item+2, &seqnum_id);
+ break;
+
+ case 'i':
+ seqnum_set = true;
+ if (sscanf(item+2, "%llx", &seqnum) != 1)
+ k = -EINVAL;
+ break;
+
+ case 'b':
+ boot_id_set = true;
+ k = sd_id128_from_string(item+2, &boot_id);
+ break;
+
+ case 'm':
+ monotonic_set = true;
+ if (sscanf(item+2, "%llx", &monotonic) != 1)
+ k = -EINVAL;
+ break;
+
+ case 't':
+ realtime_set = true;
+ if (sscanf(item+2, "%llx", &realtime) != 1)
+ k = -EINVAL;
+ break;
+
+ case 'x':
+ xor_hash_set = true;
+ if (sscanf(item+2, "%llx", &xor_hash) != 1)
+ k = -EINVAL;
+ break;
+ }
+
+ free(item);
+
+ if (k < 0)
+ return k;
+ }
+
+ if ((!seqnum_set || !seqnum_id_set) &&
+ (!monotonic_set || !boot_id_set) &&
+ !realtime_set)
+ return -EINVAL;
+
+ reset_location(j);
+
+ j->current_location.type = LOCATION_SEEK;
+
+ if (realtime_set) {
+ j->current_location.realtime = (uint64_t) realtime;
+ j->current_location.realtime_set = true;
+ }
+
+ if (seqnum_set && seqnum_id_set) {
+ j->current_location.seqnum = (uint64_t) seqnum;
+ j->current_location.seqnum_id = seqnum_id;
+ j->current_location.seqnum_set = true;
+ }