Skip to content

Instantly share code, notes, and snippets.

@mguentner
Last active October 16, 2024 08:25
Show Gist options
  • Save mguentner/5843b0f433a71f85cdf23299b49065db to your computer and use it in GitHub Desktop.
Save mguentner/5843b0f433a71f85cdf23299b49065db to your computer and use it in GitHub Desktop.
commit 982454a9a021041025fc0820c5474608261222d6
Author: Maximilian Güntner <[email protected]>
Date: Mon Oct 14 15:31:28 2024 +0200
Revert "Merge pull request #26355 from poettering/journal-no-rtc"
This reverts commit 16a9ad557de7173c182e9587a9cc0ca146293ec8, reversing
changes made to 11875a98e4f1c31e247d99e00c7774ea3653bafd.
diff --git a/TODO b/TODO
index b375327fe0..5494578f87 100644
--- a/TODO
+++ b/TODO
@@ -2193,16 +2193,6 @@ Features:
for example matching by message ID, or log level returns a list of journal
cursors as they happen.
-* journald: also collect CLOCK_BOOTTIME timestamps per log entry. Then, derive
- "corrected" CLOCK_REALTIME information on display from that and the timestamp
- info of the newest entry of the specific boot (as identified by the boot
- ID). This way, if a system comes up without a valid clock but acquires a
- better clock later, we can "fix" older entry timestamps on display, by
- calculating backwards. We cannot use CLOCK_MONOTONIC for this, since it does
- not account for suspend phases. This would then also enable us to correct the
- kmsg timestamping we consume (where we erroneously assume the clock was in
- CLOCK_MONOTONIC, but it actually is CLOCK_BOOTTIME as per kernel).
-
* in journald, write out a recognizable log record whenever the system clock is
changed ("stepped"), and in timesyncd whenever we acquire an NTP fix
("slewing"). Then, in journalctl for each boot time we come across, find
diff --git a/docs/JOURNAL_FILE_FORMAT.md b/docs/JOURNAL_FILE_FORMAT.md
index 7d3b0396ff..39647bb668 100644
--- a/docs/JOURNAL_FILE_FORMAT.md
+++ b/docs/JOURNAL_FILE_FORMAT.md
@@ -176,10 +176,8 @@ _packed_ struct Header {
le64_t data_hash_chain_depth;
le64_t field_hash_chain_depth;
/* Added in 252 */
- le32_t tail_entry_array_offset;
- le32_t tail_entry_array_n_entries;
- /* Added in 254 */
- le64_t tail_entry_offset;
+ le32_t tail_entry_array_offset; \
+ le32_t tail_entry_array_n_entries; \
};
```
@@ -252,9 +250,6 @@ field hash table, minus one.
**tail_entry_array_offset** and **tail_entry_array_n_entries** allow immediate
access to the last entry array in the global entry array chain.
-**tail_entry_offset** allow immediate access to the last entry in the journal
-file.
-
## Extensibility
The format is supposed to be extensible in order to enable future additions of
diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h
index 1b10f24aa9..ae80429666 100644
--- a/src/libsystemd/sd-journal/journal-def.h
+++ b/src/libsystemd/sd-journal/journal-def.h
@@ -242,14 +242,12 @@ enum {
/* Added in 252 */ \
le32_t tail_entry_array_offset; \
le32_t tail_entry_array_n_entries; \
- /* Added in 254 */ \
- le64_t tail_entry_offset; \
}
struct Header struct_Header__contents;
struct Header__packed struct_Header__contents _packed_;
assert_cc(sizeof(struct Header) == sizeof(struct Header__packed));
-assert_cc(sizeof(struct Header) == 272);
+assert_cc(sizeof(struct Header) == 264);
#define FSS_HEADER_SIGNATURE \
((const char[]) { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' })
diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c
index 7e941edb19..b66f74e72d 100644
--- a/src/libsystemd/sd-journal/journal-file.c
+++ b/src/libsystemd/sd-journal/journal-file.c
@@ -30,7 +30,6 @@
#include "memory-util.h"
#include "missing_threads.h"
#include "path-util.h"
-#include "prioq.h"
#include "random-util.h"
#include "set.h"
#include "sort-util.h"
@@ -280,8 +279,6 @@ JournalFile* journal_file_close(JournalFile *f) {
if (!f)
return NULL;
- assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL);
-
sd_event_source_disable_unref(f->post_change_timer);
if (f->cache_fd)
@@ -624,36 +621,6 @@ static int journal_file_verify_header(JournalFile *f) {
return -ENODATA;
}
- if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) {
- uint64_t offset = le64toh(f->header->tail_entry_offset);
-
- if (!offset_is_valid(offset, header_size, tail_object_offset))
- return -ENODATA;
-
- if (offset > 0) {
- /* When there is an entry object, then these fields must be filled. */
- if (sd_id128_is_null(f->header->tail_entry_boot_id))
- return -ENODATA;
- if (!VALID_REALTIME(le64toh(f->header->head_entry_realtime)))
- return -ENODATA;
- if (!VALID_REALTIME(le64toh(f->header->tail_entry_realtime)))
- return -ENODATA;
- if (!VALID_MONOTONIC(le64toh(f->header->tail_entry_monotonic)))
- return -ENODATA;
- } else {
- /* Otherwise, the fields must be zero. */
- if (JOURNAL_HEADER_TAIL_ENTRY_BOOT_ID(f->header) &&
- !sd_id128_is_null(f->header->tail_entry_boot_id))
- return -ENODATA;
- if (f->header->head_entry_realtime != 0)
- return -ENODATA;
- if (f->header->tail_entry_realtime != 0)
- return -ENODATA;
- if (f->header->tail_entry_monotonic != 0)
- return -ENODATA;
- }
- }
-
/* Verify number of objects */
uint64_t n_objects = le64toh(f->header->n_objects);
if (n_objects > arena_size / sizeof(ObjectHeader))
@@ -2246,8 +2213,6 @@ static int journal_file_link_entry(
f->header->tail_entry_realtime = o->entry.realtime;
f->header->tail_entry_monotonic = o->entry.monotonic;
- if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset))
- f->header->tail_entry_offset = htole64(offset);
/* Link up the items */
for (uint64_t i = 0; i < n_items; i++) {
@@ -4095,8 +4060,6 @@ int journal_file_open(
DEFAULT_COMPRESS_THRESHOLD :
MAX(MIN_COMPRESS_THRESHOLD, compress_threshold_bytes),
.strict_order = FLAGS_SET(file_flags, JOURNAL_STRICT_ORDER),
- .newest_boot_id_prioq_idx = PRIOQ_IDX_NULL,
- .last_direction = _DIRECTION_INVALID,
};
if (fname) {
diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h
index 8100388973..cf33a2bc03 100644
--- a/src/libsystemd/sd-journal/journal-file.h
+++ b/src/libsystemd/sd-journal/journal-file.h
@@ -122,15 +122,6 @@ typedef struct JournalFile {
void *fsprg_seed;
size_t fsprg_seed_size;
#endif
-
- /* When we insert this file into the per-boot priority queue 'newest_by_boot_id' in sd_journal, then by these keys */
- sd_id128_t newest_boot_id;
- sd_id128_t newest_machine_id;
- uint64_t newest_monotonic_usec;
- uint64_t newest_realtime_usec;
- unsigned newest_boot_id_prioq_idx;
- uint64_t newest_entry_offset;
- uint8_t newest_state;
} JournalFile;
typedef enum JournalFileFlags {
diff --git a/src/libsystemd/sd-journal/journal-internal.h b/src/libsystemd/sd-journal/journal-internal.h
index b95080c527..2aa7073960 100644
--- a/src/libsystemd/sd-journal/journal-internal.h
+++ b/src/libsystemd/sd-journal/journal-internal.h
@@ -85,10 +85,6 @@ struct sd_journal {
IteratedCache *files_cache;
MMapCache *mmap;
- /* a bisectable array of NewestByBootId, ordered by boot id. */
- NewestByBootId *newest_by_boot_id;
- size_t n_newest_by_boot_id;
-
Location current_location;
JournalFile *current_file;
diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c
index 0aa37260b6..9e37e914fa 100644
--- a/src/libsystemd/sd-journal/sd-journal.c
+++ b/src/libsystemd/sd-journal/sd-journal.c
@@ -35,7 +35,6 @@
#include "nulstr-util.h"
#include "origin-id.h"
#include "path-util.h"
-#include "prioq.h"
#include "process-util.h"
#include "replace-var.h"
#include "sort-util.h"
@@ -57,8 +56,6 @@
DEFINE_PRIVATE_ORIGIN_ID_HELPERS(sd_journal, journal);
static void remove_file_real(sd_journal *j, JournalFile *f);
-static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f);
-static void journal_file_unlink_newest_by_boot_id(sd_journal *j, JournalFile *f);
static int journal_put_error(sd_journal *j, int r, const char *path) {
_cleanup_free_ char *copy = NULL;
@@ -449,174 +446,9 @@ _public_ void sd_journal_flush_matches(sd_journal *j) {
detach_location(j);
}
-static int newest_by_boot_id_compare(const NewestByBootId *a, const NewestByBootId *b) {
- return id128_compare_func(&a->boot_id, &b->boot_id);
-}
-
-static void journal_file_unlink_newest_by_boot_id(sd_journal *j, JournalFile *f) {
- NewestByBootId *found;
-
- assert(j);
- assert(f);
-
- if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) /* not linked currently, hence this is a NOP */
- return;
-
- found = typesafe_bsearch(&(NewestByBootId) { .boot_id = f->newest_boot_id },
- j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare);
- assert(found);
-
- assert_se(prioq_remove(found->prioq, f, &f->newest_boot_id_prioq_idx) > 0);
- f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL;
-
- /* The prioq may be empty, but that should not cause any issue. Let's keep it. */
-}
-
-static void journal_clear_newest_by_boot_id(sd_journal *j) {
- FOREACH_ARRAY(i, j->newest_by_boot_id, j->n_newest_by_boot_id) {
- JournalFile *f;
-
- while ((f = prioq_peek(i->prioq)))
- journal_file_unlink_newest_by_boot_id(j, f);
-
- prioq_free(i->prioq);
- }
-
- j->newest_by_boot_id = mfree(j->newest_by_boot_id);
- j->n_newest_by_boot_id = 0;
-}
-
-static int journal_file_newest_monotonic_compare(const void *a, const void *b) {
- const JournalFile *x = a, *y = b;
-
- return -CMP(x->newest_monotonic_usec, y->newest_monotonic_usec); /* Invert order, we want newest first! */
-}
-
-static int journal_file_reshuffle_newest_by_boot_id(sd_journal *j, JournalFile *f) {
- NewestByBootId *found;
+_pure_ static int compare_with_location(const JournalFile *f, const Location *l, const JournalFile *current_file) {
int r;
- assert(j);
- assert(f);
-
- found = typesafe_bsearch(&(NewestByBootId) { .boot_id = f->newest_boot_id },
- j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare);
- if (found) {
- /* There's already a priority queue for this boot ID */
-
- if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) {
- r = prioq_put(found->prioq, f, &f->newest_boot_id_prioq_idx); /* Insert if we aren't in there yet */
- if (r < 0)
- return r;
- } else
- prioq_reshuffle(found->prioq, f, &f->newest_boot_id_prioq_idx); /* Reshuffle otherwise */
-
- } else {
- _cleanup_(prioq_freep) Prioq *q = NULL;
-
- /* No priority queue yet, then allocate one */
-
- assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL); /* we can't be a member either */
-
- q = prioq_new(journal_file_newest_monotonic_compare);
- if (!q)
- return -ENOMEM;
-
- r = prioq_put(q, f, &f->newest_boot_id_prioq_idx);
- if (r < 0)
- return r;
-
- if (!GREEDY_REALLOC(j->newest_by_boot_id, j->n_newest_by_boot_id + 1)) {
- f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL;
- return -ENOMEM;
- }
-
- j->newest_by_boot_id[j->n_newest_by_boot_id++] = (NewestByBootId) {
- .boot_id = f->newest_boot_id,
- .prioq = TAKE_PTR(q),
- };
-
- typesafe_qsort(j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare);
- }
-
- return 0;
-}
-
-static int journal_file_find_newest_for_boot_id(
- sd_journal *j,
- sd_id128_t id,
- JournalFile **ret) {
-
- JournalFile *prev = NULL;
- int r;
-
- assert(j);
- assert(ret);
-
- /* Before we use it, let's refresh the timestamp from the header, and reshuffle our prioq
- * accordingly. We do this only a bunch of times, to not be caught in some update loop. */
- for (unsigned n_tries = 0;; n_tries++) {
- NewestByBootId *found;
- JournalFile *f;
-
- found = typesafe_bsearch(&(NewestByBootId) { .boot_id = id },
- j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare);
-
- f = found ? prioq_peek(found->prioq) : NULL;
- if (!f)
- return log_debug_errno(SYNTHETIC_ERRNO(ENODATA),
- "Requested delta for boot ID %s, but we have no information about that boot ID.", SD_ID128_TO_STRING(id));
-
- if (f == prev || n_tries >= 5) {
- /* This was already the best answer in the previous run, or we tried too often, use it */
- *ret = f;
- return 0;
- }
-
- prev = f;
-
- /* Let's read the journal file's current timestamp once, before we return it, maybe it has changed. */
- r = journal_file_read_tail_timestamp(j, f);
- if (r < 0)
- return log_debug_errno(r, "Failed to read tail timestamp while trying to find newest journal file for boot ID %s.", SD_ID128_TO_STRING(id));
- if (r == 0) {
- /* No new entry found. */
- *ret = f;
- return 0;
- }
-
- /* Refreshing the timestamp we read might have reshuffled the prioq, hence let's check the
- * prioq again and only use the information once we reached an equilibrium or hit a limit */
- }
-}
-
-static int compare_boot_ids(sd_journal *j, sd_id128_t a, sd_id128_t b) {
- JournalFile *x, *y;
-
- assert(j);
-
- /* Try to find the newest open journal file for the two boot ids */
- if (journal_file_find_newest_for_boot_id(j, a, &x) < 0 ||
- journal_file_find_newest_for_boot_id(j, b, &y) < 0)
- return 0;
-
- /* Only compare the boot id timestamps if they originate from the same machine. If they are from
- * different machines, then we timestamps of the boot ids might be as off as the timestamps on the
- * entries and hence not useful for comparing. */
- if (!sd_id128_equal(x->newest_machine_id, y->newest_machine_id))
- return 0;
-
- return CMP(x->newest_realtime_usec, y->newest_realtime_usec);
-}
-
-static int compare_with_location(
- sd_journal *j,
- const JournalFile *f,
- const Location *l,
- const JournalFile *current_file) {
- int r;
-
- assert(j);
assert(f);
assert(l);
assert(f->location_type == LOCATION_SEEK);
@@ -636,30 +468,29 @@ static int compare_with_location(
if (l->seqnum_set &&
sd_id128_equal(f->header->seqnum_id, l->seqnum_id)) {
+
r = CMP(f->current_seqnum, l->seqnum);
if (r != 0)
return r;
}
- if (l->monotonic_set) {
- /* If both arguments have the same boot ID, then we can compare the monotonic timestamps. If
- * they are distinct, then we might able to lookup the timestamps of those boot IDs (if they
- * are from the same machine) and order by that. */
- if (sd_id128_equal(f->current_boot_id, l->boot_id))
- r = CMP(f->current_monotonic, l->monotonic);
- else
- r = compare_boot_ids(j, f->current_boot_id, l->boot_id);
+ if (l->monotonic_set &&
+ sd_id128_equal(f->current_boot_id, l->boot_id)) {
+
+ r = CMP(f->current_monotonic, l->monotonic);
if (r != 0)
return r;
}
if (l->realtime_set) {
+
r = CMP(f->current_realtime, l->realtime);
if (r != 0)
return r;
}
if (l->xor_hash_set) {
+
r = CMP(f->current_xor_hash, l->xor_hash);
if (r != 0)
return r;
@@ -942,8 +773,6 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc
assert(j);
assert(f);
- (void) journal_file_read_tail_timestamp(j, f);
-
n_entries = le64toh(f->header->n_entries);
/* If we hit EOF before, we don't need to look into this file again
@@ -988,7 +817,7 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc
if (j->current_location.type == LOCATION_DISCRETE) {
int k;
- k = compare_with_location(j, f, &j->current_location, j->current_file);
+ k = compare_with_location(f, &j->current_location, j->current_file);
found = direction == DIRECTION_DOWN ? k > 0 : k < 0;
} else
@@ -1005,10 +834,9 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc
}
}
-static int compare_locations(sd_journal *j, JournalFile *af, JournalFile *bf) {
+static int compare_locations(JournalFile *af, JournalFile *bf) {
int r;
- assert(j);
assert(af);
assert(af->header);
assert(bf);
@@ -1035,14 +863,12 @@ static int compare_locations(sd_journal *j, JournalFile *af, JournalFile *bf) {
* make the best of it and compare by time. */
}
- if (sd_id128_equal(af->current_boot_id, bf->current_boot_id))
+ if (sd_id128_equal(af->current_boot_id, bf->current_boot_id)) {
/* If the boot id matches, compare monotonic time */
r = CMP(af->current_monotonic, bf->current_monotonic);
- else
- /* If they don't match try to compare boot IDs */
- r = compare_boot_ids(j, af->current_boot_id, bf->current_boot_id);
- if (r != 0)
- return r;
+ if (r != 0)
+ return r;
+ }
/* Otherwise, compare UTC time */
r = CMP(af->current_realtime, bf->current_realtime);
@@ -1086,7 +912,7 @@ static int real_journal_next(sd_journal *j, direction_t direction) {
else {
int k;
- k = compare_locations(j, f, new_file);
+ k = compare_locations(f, new_file);
found = direction == DIRECTION_DOWN ? k < 0 : k > 0;
}
@@ -1564,7 +1390,6 @@ static int add_any_file(
* which are gone. */
f->last_seen_generation = j->generation;
- (void) journal_file_read_tail_timestamp(j, f);
return 0;
}
@@ -1605,7 +1430,6 @@ static int add_any_file(
track_file_disposition(j, f);
check_network(j, f->fd);
- (void) journal_file_read_tail_timestamp(j, f);
j->current_invalidate_counter++;
@@ -1729,7 +1553,6 @@ static void remove_file_real(sd_journal *j, JournalFile *f) {
j->fields_file_lost = true;
}
- journal_file_unlink_newest_by_boot_id(j, f);
(void) journal_file_close(f);
j->current_invalidate_counter++;
@@ -2469,10 +2292,10 @@ fail:
}
_public_ void sd_journal_close(sd_journal *j) {
- if (!j || journal_origin_changed(j))
- return;
+ Directory *d;
- journal_clear_newest_by_boot_id(j);
+ if (!j)
+ return;
sd_journal_flush_matches(j);
@@ -2504,112 +2327,6 @@ _public_ void sd_journal_close(sd_journal *j) {
free(j);
}
-static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) {
- uint64_t offset, mo, rt;
- sd_id128_t id;
- ObjectType type;
- Object *o;
- int r;
-
- assert(j);
- assert(f);
- assert(f->header);
-
- /* Tries to read the timestamp of the most recently written entry. */
-
- if (FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE) && f->newest_entry_offset != 0)
- return 0; /* We have already read the file, and we assume that the file is immutable. */
-
- if (f->header->state == f->newest_state &&
- f->header->state == STATE_ARCHIVED &&
- f->newest_entry_offset != 0)
- return 0; /* We have already read archived file. */
-
- if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) {
- offset = le64toh(READ_NOW(f->header->tail_entry_offset));
- type = OBJECT_ENTRY;
- } else {
- offset = le64toh(READ_NOW(f->header->tail_object_offset));
- type = OBJECT_UNUSED;
- }
- if (offset == 0)
- return -ENODATA; /* not a single object/entry, hence no tail timestamp */
- if (offset == f->newest_entry_offset)
- return 0; /* No new entry is added after we read last time. */
-
- /* Move to the last object in the journal file, in the hope it is an entry (which it usually will
- * be). If we lack the "tail_entry_offset" field in the header, we specify the type as OBJECT_UNUSED
- * here, since we cannot be sure what the last object will be, and want no noisy logging if it isn't
- * an entry. We instead check after figuring out the pointer. */
- r = journal_file_move_to_object(f, type, offset, &o);
- if (r < 0) {
- log_debug_errno(r, "Failed to move to last object in journal file, ignoring: %m");
- o = NULL;
- offset = 0;
- }
- if (o && o->object.type == OBJECT_ENTRY) {
- /* Yay, last object is an entry, let's use the data. */
- id = o->entry.boot_id;
- mo = le64toh(o->entry.monotonic);
- rt = le64toh(o->entry.realtime);
- } else {
- /* So the object is not an entry or we couldn't access it? In that case, let's read the most
- * recent entry timestamps from the header. It's equally good. Unfortunately though, in old
- * versions of the journal the boot ID in the header doesn't have to match the monotonic
- * timestamp of the header. Let's check the header flag that indicates whether this strictly
- * matches first hence, before using the data. */
-
- if (JOURNAL_HEADER_TAIL_ENTRY_BOOT_ID(f->header) && f->header->state == STATE_ARCHIVED) {
- mo = le64toh(f->header->tail_entry_monotonic);
- rt = le64toh(f->header->tail_entry_realtime);
- id = f->header->tail_entry_boot_id;
- offset = UINT64_MAX;
- } else {
- /* Otherwise let's find the last entry manually (this possibly means traversing the
- * chain of entry arrays, till the end */
- r = journal_file_next_entry(f, 0, DIRECTION_UP, &o, offset == 0 ? &offset : NULL);
- if (r < 0)
- return r;
- if (r == 0)
- return -ENODATA;
-
- id = o->entry.boot_id;
- mo = le64toh(o->entry.monotonic);
- rt = le64toh(o->entry.realtime);
- }
- }
-
- if (mo > rt) /* monotonic clock is further ahead than realtime? that's weird, refuse to use the data */
- return -ENODATA;
-
- if (offset == f->newest_entry_offset) {
- /* Cached data and the current one should be equivalent. */
- if (!sd_id128_equal(f->newest_machine_id, f->header->machine_id) ||
- !sd_id128_equal(f->newest_boot_id, id) ||
- f->newest_monotonic_usec != mo ||
- f->newest_realtime_usec != rt)
- return -EBADMSG;
-
- return 0; /* No new entry is added after we read last time. */
- }
-
- if (!sd_id128_equal(f->newest_boot_id, id))
- journal_file_unlink_newest_by_boot_id(j, f);
-
- f->newest_boot_id = id;
- f->newest_monotonic_usec = mo;
- f->newest_realtime_usec = rt;
- f->newest_machine_id = f->header->machine_id;
- f->newest_entry_offset = offset;
- f->newest_state = f->header->state;
-
- r = journal_file_reshuffle_newest_by_boot_id(j, f);
- if (r < 0)
- return r;
-
- return 1; /* Updated. */
-}
-
_public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
JournalFile *f;
Object *o;
diff --git a/test/TEST-04-JOURNAL/test.sh b/test/TEST-04-JOURNAL/test.sh
index a7aa71fce3..0a3bfb545c 100755
--- a/test/TEST-04-JOURNAL/test.sh
+++ b/test/TEST-04-JOURNAL/test.sh
@@ -7,23 +7,4 @@ TEST_DESCRIPTION="Journal-related tests"
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
-test_append_files() {
- local workspace="${1:?}"
- local dropin_dir
-
- image_install curl setterm unzstd
- image_install -o openssl
- # Necessary for RH-based systems, otherwise MHD fails with:
- # microhttpd: Failed to initialise TLS session.
- image_install -o /etc/crypto-policies/back-ends/gnutls.config
-
- # Since we nuke the journal repeatedly during this test, let's redirect
- # stdout/stderr to the console as well to make the test a bit more debug-able.
- if ! get_bool "${INTERACTIVE_DEBUG:-}"; then
- dropin_dir="${workspace:?}/etc/systemd/system/TEST-04-JOURNAL.service.d/"
- mkdir -p "$dropin_dir"
- printf '[Service]\nStandardOutput=journal+console\nStandardError=journal+console' >"$dropin_dir/99-stdout.conf"
- fi
-}
-
do_test "$@"
diff --git a/test/units/testsuite-04.sh b/test/units/testsuite-04.sh
new file mode 100755
index 0000000000..3bf7577a0e
--- /dev/null
+++ b/test/units/testsuite-04.sh
@@ -0,0 +1,278 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# Limit the maximum journal size
+trap "journalctl --rotate --vacuum-size=16M" EXIT
+
+# Rotation/flush test, see https://github.com/systemd/systemd/issues/19895
+journalctl --relinquish-var
+for _ in {0..50}; do
+ dd if=/dev/urandom bs=1M count=1 | base64 | systemd-cat
+done
+journalctl --rotate
+journalctl --flush
+journalctl --sync
+
+# Reset the ratelimit buckets for the subsequent tests below.
+systemctl restart systemd-journald
+
+# Test stdout stream
+
+# Skip empty lines
+ID=$(journalctl --new-id128 | sed -n 2p)
+: >/expected
+printf $'\n\n\n' | systemd-cat -t "$ID" --level-prefix false
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+ID=$(journalctl --new-id128 | sed -n 2p)
+: >/expected
+printf $'<5>\n<6>\n<7>\n' | systemd-cat -t "$ID" --level-prefix true
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+# Remove trailing spaces
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf "Trailing spaces\n">/expected
+printf $'<5>Trailing spaces \t \n' | systemd-cat -t "$ID" --level-prefix true
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf "Trailing spaces\n">/expected
+printf $'Trailing spaces \t \n' | systemd-cat -t "$ID" --level-prefix false
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+# Don't remove leading spaces
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf $' \t Leading spaces\n'>/expected
+printf $'<5> \t Leading spaces\n' | systemd-cat -t "$ID" --level-prefix true
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf $' \t Leading spaces\n'>/expected
+printf $' \t Leading spaces\n' | systemd-cat -t "$ID" --level-prefix false
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+
+# --output-fields restricts output
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf $'foo' | systemd-cat -t "$ID" --level-prefix false
+journalctl --sync
+journalctl -b -o export --output-fields=MESSAGE,FOO --output-fields=PRIORITY,MESSAGE -t "$ID" >/output
+[[ $(grep -c . /output) -eq 8 ]]
+grep -q '^__CURSOR=' /output
+grep -q '^MESSAGE=foo$' /output
+grep -q '^PRIORITY=6$' /output
+grep '^FOO=' /output && { echo 'unexpected success'; exit 1; }
+grep '^SYSLOG_FACILITY=' /output && { echo 'unexpected success'; exit 1; }
+
+# `-b all` negates earlier use of -b (-b and -m are otherwise exclusive)
+journalctl -b -1 -b all -m >/dev/null
+
+# -b always behaves like -b0
+journalctl -q -b-1 -b0 | head -1 >/expected
+journalctl -q -b-1 -b | head -1 >/output
+cmp /expected /output
+# ... even when another option follows (both of these should fail due to -m)
+{ journalctl -ball -b0 -m 2>&1 || :; } | head -1 >/expected
+{ journalctl -ball -b -m 2>&1 || :; } | head -1 >/output
+cmp /expected /output
+
+# https://github.com/systemd/systemd/issues/13708
+ID=$(systemd-id128 new)
+systemd-cat -t "$ID" bash -c 'echo parent; (echo child) & wait' &
+PID=$!
+wait $PID
+journalctl --sync
+# We can drop this grep when https://github.com/systemd/systemd/issues/13937
+# has a fix.
+journalctl -b -o export -t "$ID" --output-fields=_PID | grep '^_PID=' >/output
+[[ $(grep -c . /output) -eq 2 ]]
+grep -q "^_PID=$PID" /output
+grep -vq "^_PID=$PID" /output
+
+# https://github.com/systemd/systemd/issues/15654
+ID=$(journalctl --new-id128 | sed -n 2p)
+printf "This will\nusually fail\nand be truncated\n">/expected
+systemd-cat -t "$ID" /bin/sh -c 'env echo -n "This will";echo;env echo -n "usually fail";echo;env echo -n "and be truncated";echo;'
+journalctl --sync
+journalctl -b -o cat -t "$ID" >/output
+cmp /expected /output
+[[ $(journalctl -b -o cat -t "$ID" --output-fields=_TRANSPORT | grep -Pc "^stdout$") -eq 3 ]]
+[[ $(journalctl -b -o cat -t "$ID" --output-fields=_LINE_BREAK | grep -Pc "^pid-change$") -eq 3 ]]
+[[ $(journalctl -b -o cat -t "$ID" --output-fields=_PID | sort -u | grep -c "^.*$") -eq 3 ]]
+[[ $(journalctl -b -o cat -t "$ID" --output-fields=MESSAGE | grep -Pc "^(This will|usually fail|and be truncated)$") -eq 3 ]]
+
+# test that LogLevelMax can also suppress logging about services, not only by services
+systemctl start silent-success
+journalctl --sync
+[[ -z "$(journalctl -b -q -u silent-success.service)" ]]
+
+# Exercise the matching machinery
+SYSTEMD_LOG_LEVEL=debug journalctl -b -n 1 /dev/null /dev/zero /dev/null /dev/null /dev/null
+journalctl -b -n 1 /bin/true /bin/false
+journalctl -b -n 1 /bin/true + /bin/false
+journalctl -b -n 1 -r --unit "systemd*"
+
+systemd-run --user -M "[email protected]" /bin/echo hello
+journalctl --sync
+journalctl -b -n 1 -r --user-unit "*"
+
+(! journalctl -b /dev/lets-hope-this-doesnt-exist)
+(! journalctl -b /dev/null /dev/zero /dev/this-also-shouldnt-exist)
+(! journalctl -b --unit "this-unit-should-not-exist*")
+
+# Facilities & priorities
+journalctl --facility help
+journalctl --facility kern -n 1
+journalctl --facility syslog --priority 0..3 -n 1
+journalctl --facility syslog --priority 3..0 -n 1
+journalctl --facility user --priority 0..0 -n 1
+journalctl --facility daemon --priority warning -n 1
+journalctl --facility daemon --priority warning..info -n 1
+journalctl --facility daemon --priority notice..crit -n 1
+journalctl --facility daemon --priority 5..crit -n 1
+
+(! journalctl --facility hopefully-an-unknown-facility)
+(! journalctl --priority hello-world)
+(! journalctl --priority 0..128)
+(! journalctl --priority 0..systemd)
+
+# Other options
+journalctl --disk-usage
+journalctl --dmesg -n 1
+journalctl --fields
+journalctl --list-boots
+journalctl --update-catalog
+journalctl --list-catalog
+
+# Add new tests before here, the journald restarts below
+# may make tests flappy.
+
+# Don't lose streams on restart
+systemctl start forever-print-hola
+sleep 3
+systemctl restart systemd-journald
+sleep 3
+systemctl stop forever-print-hola
+[[ ! -f "/i-lose-my-logs" ]]
+
+# https://github.com/systemd/systemd/issues/4408
+rm -f /i-lose-my-logs
+systemctl start forever-print-hola
+sleep 3
+systemctl kill --signal=SIGKILL systemd-journald
+sleep 3
+[[ ! -f "/i-lose-my-logs" ]]
+
+# https://github.com/systemd/systemd/issues/15528
+journalctl --follow --file=/var/log/journal/*/* | head -n1 || [[ $? -eq 1 ]]
+
+function add_logs_filtering_override() {
+ UNIT=${1:?}
+ OVERRIDE_NAME=${2:?}
+ LOG_FILTER=${3:-""}
+
+ mkdir -p /etc/systemd/system/"$UNIT".d/
+ echo "[Service]" >/etc/systemd/system/"$UNIT".d/"${OVERRIDE_NAME}".conf
+ echo "LogFilterPatterns=$LOG_FILTER" >>/etc/systemd/system/"$UNIT".d/"${OVERRIDE_NAME}".conf
+ systemctl daemon-reload
+}
+
+function run_service_and_fetch_logs() {
+ UNIT=$1
+
+ START=$(date '+%Y-%m-%d %T.%6N')
+ systemctl restart "$UNIT"
+ sleep .5
+ journalctl --sync
+ END=$(date '+%Y-%m-%d %T.%6N')
+
+ journalctl -q -u "$UNIT" -S "$START" -U "$END" -p notice
+ systemctl stop "$UNIT"
+}
+
+function is_xattr_supported() {
+ START=$(date '+%Y-%m-%d %T.%6N')
+ systemd-run --unit text_xattr --property LogFilterPatterns=log sh -c "sleep .5"
+ sleep .5
+ journalctl --sync
+ END=$(date '+%Y-%m-%d %T.%6N')
+ systemctl stop text_xattr
+
+ if journalctl -q -u "text_xattr" -S "$START" -U "$END" --grep "Failed to set 'user.journald_log_filter_patterns' xattr.*not supported$"; then
+ return 1
+ fi
+
+ return 0
+}
+
+if is_xattr_supported; then
+ # Accept all log messages
+ add_logs_filtering_override "logs-filtering.service" "00-reset" ""
+ [[ -n $(run_service_and_fetch_logs "logs-filtering.service") ]]
+
+ add_logs_filtering_override "logs-filtering.service" "01-allow-all" ".*"
+ [[ -n $(run_service_and_fetch_logs "logs-filtering.service") ]]
+
+ # Discard all log messages
+ add_logs_filtering_override "logs-filtering.service" "02-discard-all" "~.*"
+ [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]]
+
+ # Accept all test messages
+ add_logs_filtering_override "logs-filtering.service" "03-reset" ""
+ [[ -n $(run_service_and_fetch_logs "logs-filtering.service") ]]
+
+ # Discard all test messages
+ add_logs_filtering_override "logs-filtering.service" "04-discard-gg" "~.*gg.*"
+ [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]]
+
+ # Deny filter takes precedence
+ add_logs_filtering_override "logs-filtering.service" "05-allow-all-but-too-late" ".*"
+ [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]]
+
+ # Use tilde in a deny pattern
+ add_logs_filtering_override "logs-filtering.service" "06-reset" ""
+ add_logs_filtering_override "logs-filtering.service" "07-prevent-tilde" "~~more~"
+ [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]]
+
+ # Only allow a pattern that won't be matched
+ add_logs_filtering_override "logs-filtering.service" "08-reset" ""
+ add_logs_filtering_override "logs-filtering.service" "09-allow-only-non-existing" "non-existing string"
+ [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]]
+
+ # Allow a pattern starting with a tilde
+ add_logs_filtering_override "logs-filtering.service" "10-allow-with-escape-char" "\x7emore~"
+ [[ -n $(run_service_and_fetch_logs "logs-filtering.service") ]]
+
+ add_logs_filtering_override "delegated-cgroup-filtering.service" "00-allow-all" ".*"
+ [[ -n $(run_service_and_fetch_logs "delegated-cgroup-filtering.service") ]]
+
+ add_logs_filtering_override "delegated-cgroup-filtering.service" "01-discard-hello" "~hello"
+ [[ -z $(run_service_and_fetch_logs "delegated-cgroup-filtering.service") ]]
+
+ rm -rf /etc/systemd/system/logs-filtering.service.d
+ rm -rf /etc/systemd/system/delegated-cgroup-filtering.service.d
+fi
+
+# Check that the seqnum field at least superficially works
+systemd-cat echo "ya"
+journalctl --sync
+SEQNUM1=$(journalctl -o export -n 1 | grep -Ea "^__SEQNUM=" | cut -d= -f2)
+systemd-cat echo "yo"
+journalctl --sync
+SEQNUM2=$(journalctl -o export -n 1 | grep -Ea "^__SEQNUM=" | cut -d= -f2)
+test "$SEQNUM2" -gt "$SEQNUM1"
+
+touch /testok
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment