+ if (!strv_isempty(args) && !have_term) {
+ log_error("\"+\" can only be used between terms");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int boot_id_cmp(const void *a, const void *b) {
+ uint64_t _a, _b;
+
+ _a = ((const boot_id_t *)a)->first;
+ _b = ((const boot_id_t *)b)->first;
+
+ return _a < _b ? -1 : (_a > _b ? 1 : 0);
+}
+
+static int list_boots(sd_journal *j) {
+ int r;
+ const void *data;
+ unsigned int count = 0;
+ int w, i;
+ size_t length, allocated = 0;
+ boot_id_t *id;
+ _cleanup_free_ boot_id_t *all_ids = NULL;
+
+ r = sd_journal_query_unique(j, "_BOOT_ID");
+ if (r < 0)
+ return r;
+
+ pager_open_if_enabled();
+
+ SD_JOURNAL_FOREACH_UNIQUE(j, data, length) {
+ assert(startswith(data, "_BOOT_ID="));
+
+ if (!GREEDY_REALLOC(all_ids, allocated, count + 1))
+ return log_oom();
+
+ id = &all_ids[count];
+
+ r = sd_id128_from_string(((const char *)data) + strlen("_BOOT_ID="), &id->id);
+ if (r < 0)
+ continue;
+
+ r = sd_journal_add_match(j, data, length);
+ if (r < 0)
+ return r;
+
+ r = sd_journal_seek_head(j);
+ if (r < 0)
+ return r;
+
+ r = sd_journal_next(j);
+ if (r < 0)
+ return r;
+ else if (r == 0)
+ goto flush;
+
+ r = sd_journal_get_realtime_usec(j, &id->first);
+ if (r < 0)
+ return r;
+
+ r = sd_journal_seek_tail(j);
+ if (r < 0)
+ return r;
+
+ r = sd_journal_previous(j);
+ if (r < 0)
+ return r;
+ else if (r == 0)
+ goto flush;
+
+ r = sd_journal_get_realtime_usec(j, &id->last);
+ if (r < 0)
+ return r;
+
+ count++;
+ flush:
+ sd_journal_flush_matches(j);
+ }
+
+ qsort_safe(all_ids, count, sizeof(boot_id_t), boot_id_cmp);
+
+ /* numbers are one less, but we need an extra char for the sign */
+ w = DECIMAL_STR_WIDTH(count - 1) + 1;
+
+ for (id = all_ids, i = 0; id < all_ids + count; id++, i++) {
+ char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX];
+
+ printf("% *i " SD_ID128_FORMAT_STR " %s—%s\n",
+ w, i - count + 1,
+ SD_ID128_FORMAT_VAL(id->id),
+ format_timestamp_maybe_utc(a, sizeof(a), id->first),
+ format_timestamp_maybe_utc(b, sizeof(b), id->last));
+ }
+
+ return 0;
+}
+
+static int get_relative_boot_id(sd_journal *j, sd_id128_t *boot_id, int relative) {
+ int r;
+ const void *data;
+ unsigned int count = 0;
+ size_t length, allocated = 0;
+ boot_id_t ref_boot_id = {SD_ID128_NULL}, *id;
+ _cleanup_free_ boot_id_t *all_ids = NULL;
+
+ assert(j);
+ assert(boot_id);
+
+ r = sd_journal_query_unique(j, "_BOOT_ID");
+ if (r < 0)
+ return r;
+
+ SD_JOURNAL_FOREACH_UNIQUE(j, data, length) {
+ if (length < strlen("_BOOT_ID="))
+ continue;
+
+ if (!GREEDY_REALLOC(all_ids, allocated, count + 1))
+ return log_oom();
+
+ id = &all_ids[count];
+
+ r = sd_id128_from_string(((const char *)data) + strlen("_BOOT_ID="), &id->id);
+ if (r < 0)
+ continue;
+
+ r = sd_journal_add_match(j, data, length);
+ if (r < 0)
+ return r;
+
+ r = sd_journal_seek_head(j);
+ if (r < 0)
+ return r;
+
+ r = sd_journal_next(j);
+ if (r < 0)
+ return r;
+ else if (r == 0)
+ goto flush;
+
+ r = sd_journal_get_realtime_usec(j, &id->first);
+ if (r < 0)
+ return r;
+
+ if (sd_id128_equal(id->id, *boot_id))
+ ref_boot_id = *id;
+
+ count++;
+ flush:
+ sd_journal_flush_matches(j);
+ }
+
+ qsort_safe(all_ids, count, sizeof(boot_id_t), boot_id_cmp);
+
+ if (sd_id128_equal(*boot_id, SD_ID128_NULL)) {
+ if (relative > (int) count || relative <= -(int)count)
+ return -EADDRNOTAVAIL;
+
+ *boot_id = all_ids[(relative <= 0)*count + relative - 1].id;
+ } else {
+ id = bsearch(&ref_boot_id, all_ids, count, sizeof(boot_id_t), boot_id_cmp);
+
+ if (!id ||
+ relative <= 0 ? (id - all_ids) + relative < 0 :
+ (id - all_ids) + relative >= (int) count)
+ return -EADDRNOTAVAIL;
+
+ *boot_id = (id + relative)->id;
+ }
+
+ return 0;
+}
+
+static int add_boot(sd_journal *j) {
+ char match[9+32+1] = "_BOOT_ID=";
+ int r;
+
+ assert(j);
+
+ if (!arg_boot)
+ return 0;
+
+ if (arg_boot_offset == 0 && sd_id128_equal(arg_boot_id, SD_ID128_NULL))
+ return add_match_this_boot(j, arg_machine);
+
+ r = get_relative_boot_id(j, &arg_boot_id, arg_boot_offset);
+ if (r < 0) {
+ if (sd_id128_equal(arg_boot_id, SD_ID128_NULL))
+ log_error("Failed to look up boot %+i: %s", arg_boot_offset, strerror(-r));
+ else
+ log_error("Failed to look up boot ID "SD_ID128_FORMAT_STR"%+i: %s",
+ SD_ID128_FORMAT_VAL(arg_boot_id), arg_boot_offset, strerror(-r));
+ return r;
+ }
+
+ sd_id128_to_string(arg_boot_id, match + 9);
+
+ r = sd_journal_add_match(j, match, sizeof(match) - 1);
+ if (r < 0) {
+ log_error("Failed to add match: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int add_dmesg(sd_journal *j) {
+ int r;
+ assert(j);
+
+ if (!arg_dmesg)
+ return 0;
+
+ r = sd_journal_add_match(j, "_TRANSPORT=kernel", strlen("_TRANSPORT=kernel"));
+ if (r < 0) {
+ log_error("Failed to add match: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int get_possible_units(sd_journal *j,
+ const char *fields,
+ char **patterns,
+ Set **units) {
+ _cleanup_set_free_free_ Set *found;
+ const char *field;
+ int r;
+
+ found = set_new(&string_hash_ops);
+ if (!found)
+ return log_oom();
+
+ NULSTR_FOREACH(field, fields) {
+ const void *data;
+ size_t size;
+
+ r = sd_journal_query_unique(j, field);
+ if (r < 0)
+ return r;
+
+ SD_JOURNAL_FOREACH_UNIQUE(j, data, size) {
+ char **pattern, *eq;
+ size_t prefix;
+ _cleanup_free_ char *u = NULL;
+
+ eq = memchr(data, '=', size);
+ if (eq)
+ prefix = eq - (char*) data + 1;
+ else
+ prefix = 0;
+
+ u = strndup((char*) data + prefix, size - prefix);
+ if (!u)
+ return log_oom();
+
+ STRV_FOREACH(pattern, patterns)
+ if (fnmatch(*pattern, u, FNM_NOESCAPE) == 0) {
+ log_debug("Matched %s with pattern %s=%s", u, field, *pattern);
+
+ r = set_consume(found, u);
+ u = NULL;
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+ break;
+ }
+ }
+ }
+
+ *units = found;
+ found = NULL;
+ return 0;
+}
+
+/* This list is supposed to return the superset of unit names
+ * possibly matched by rules added with add_matches_for_unit... */
+#define SYSTEM_UNITS \
+ "_SYSTEMD_UNIT\0" \
+ "COREDUMP_UNIT\0" \
+ "UNIT\0" \
+ "OBJECT_SYSTEMD_UNIT\0" \
+ "_SYSTEMD_SLICE\0"
+
+/* ... and add_matches_for_user_unit */
+#define USER_UNITS \
+ "_SYSTEMD_USER_UNIT\0" \
+ "USER_UNIT\0" \
+ "COREDUMP_USER_UNIT\0" \
+ "OBJECT_SYSTEMD_USER_UNIT\0"
+
+static int add_units(sd_journal *j) {
+ _cleanup_strv_free_ char **patterns = NULL;
+ int r, count = 0;
+ char **i;
+
+ assert(j);
+
+ STRV_FOREACH(i, arg_system_units) {
+ _cleanup_free_ char *u = NULL;
+
+ u = unit_name_mangle(*i, MANGLE_GLOB);
+ if (!u)
+ return log_oom();
+
+ if (string_is_glob(u)) {
+ r = strv_push(&patterns, u);
+ if (r < 0)
+ return r;
+ u = NULL;
+ } else {
+ r = add_matches_for_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count ++;
+ }
+ }
+
+ if (!strv_isempty(patterns)) {
+ _cleanup_set_free_free_ Set *units = NULL;
+ Iterator it;
+ char *u;
+
+ r = get_possible_units(j, SYSTEM_UNITS, patterns, &units);
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(u, units, it) {
+ r = add_matches_for_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count ++;
+ }
+ }
+
+ strv_free(patterns);
+ patterns = NULL;