From: Samuel BF <8824801-samuelbf@users.noreply.gitlab.com> Date: Thu, 5 Oct 2023 19:39:45 +0000 (+0200) Subject: journal-gatewayd: add since/until parameters for /entries X-Git-Tag: v256-rc1~1573 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=435c372ce5059082212d37ac7039844f14f34a80;p=thirdparty%2Fsystemd.git journal-gatewayd: add since/until parameters for /entries Request with Range header like 'entries=:' (with a colon at the end, invalid syntax per the doc), is now rejected with error 400 Bad Request. fix #4883 --- diff --git a/man/systemd-journal-gatewayd.service.xml b/man/systemd-journal-gatewayd.service.xml index 45adc2a042f..80d3bf0820f 100644 --- a/man/systemd-journal-gatewayd.service.xml +++ b/man/systemd-journal-gatewayd.service.xml @@ -277,9 +277,13 @@ + + + where cursor is a cursor string, + since and until are timestamps (seconds since 1970-01-01 00:00:00 UTC), num_skip is an integer, num_entries is an unsigned integer. diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 09194710cb6..9f0d0ec34af 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -32,6 +32,7 @@ #include "pretty-print.h" #include "sigbus.h" #include "signal-util.h" +#include "time-util.h" #include "tmpfile-util.h" #define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC) @@ -54,9 +55,10 @@ typedef struct RequestMeta { OutputMode mode; char *cursor; + usec_t since, until; int64_t n_skip; uint64_t n_entries; - bool n_entries_set; + bool n_entries_set, since_set, until_set; FILE *tmp; uint64_t delta, size; @@ -211,6 +213,17 @@ static ssize_t request_reader_entries( return MHD_CONTENT_READER_END_OF_STREAM; } + if (m->until_set) { + usec_t usec; + + r = sd_journal_get_realtime_usec(m->journal, &usec); + if (r < 0) { + log_error_errno(r, "Failed to determine timestamp: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + if (usec > m->until) + return MHD_CONTENT_READER_END_OF_STREAM; + } pos -= m->size; m->delta += m->size; @@ -292,60 +305,56 @@ static int request_parse_accept( return 0; } -static int request_parse_range( +static int request_parse_range_skip_and_n_entries( RequestMeta *m, - struct MHD_Connection *connection) { + const char *colon) { - const char *range, *colon, *colon2; + const char *p, *colon2; int r; - assert(m); - assert(connection); + colon2 = strchr(colon + 1, ':'); + if (colon2) { + _cleanup_free_ char *t = NULL; - range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); - if (!range) - return 0; + t = strndup(colon + 1, colon2 - colon - 1); + if (!t) + return -ENOMEM; - if (!startswith(range, "entries=")) - return 0; - - range += 8; - range += strspn(range, WHITESPACE); + r = safe_atoi64(t, &m->n_skip); + if (r < 0) + return r; + } - colon = strchr(range, ':'); - if (!colon) - m->cursor = strdup(range); - else { - const char *p; + p = (colon2 ?: colon) + 1; + r = safe_atou64(p, &m->n_entries); + if (r < 0) + return r; - colon2 = strchr(colon + 1, ':'); - if (colon2) { - _cleanup_free_ char *t = NULL; + if (m->n_entries <= 0) + return -EINVAL; - t = strndup(colon + 1, colon2 - colon - 1); - if (!t) - return -ENOMEM; + m->n_entries_set = true; - r = safe_atoi64(t, &m->n_skip); - if (r < 0) - return r; - } + return 0; +} - p = (colon2 ?: colon) + 1; - if (*p) { - r = safe_atou64(p, &m->n_entries); - if (r < 0) - return r; +static int request_parse_range_entries( + RequestMeta *m, + const char *entries_request) { - if (m->n_entries <= 0) - return -EINVAL; + const char *colon; + int r; - m->n_entries_set = true; - } + colon = strchr(entries_request, ':'); + if (!colon) + m->cursor = strdup(entries_request); + else { + r = request_parse_range_skip_and_n_entries(m, colon); + if (r < 0) + return r; - m->cursor = strndup(range, colon - range); + m->cursor = strndup(entries_request, colon - entries_request); } - if (!m->cursor) return -ENOMEM; @@ -356,6 +365,88 @@ static int request_parse_range( return 0; } +static int request_parse_range_time( + RequestMeta *m, + const char *time_request) { + + _cleanup_free_ char *until = NULL; + const char *colon; + int r; + + colon = strchr(time_request, ':'); + if (!colon) + return -EINVAL; + + if (colon - time_request > 0) { + _cleanup_free_ char *t = NULL; + + t = strndup(time_request, colon - time_request); + if (!t) + return -ENOMEM; + + r = parse_sec(t, &m->since); + if (r < 0) + return r; + + m->since_set = true; + } + + time_request = colon + 1; + colon = strchr(time_request, ':'); + if (!colon) + until = strdup(time_request); + else { + r = request_parse_range_skip_and_n_entries(m, colon); + if (r < 0) + return r; + + until = strndup(time_request, colon - time_request); + } + if (!until) + return -ENOMEM; + + if (!isempty(until)) { + r = parse_sec(until, &m->until); + if (r < 0) + return r; + + m->until_set = true; + if (m->until < m->since) + return -EINVAL; + } + + return 0; +} + +static int request_parse_range( + RequestMeta *m, + struct MHD_Connection *connection) { + + const char *range, *range_after_eq; + + assert(m); + assert(connection); + + range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); + if (!range) + return 0; + + m->n_skip = 0; + range_after_eq = startswith(range, "entries="); + if (range_after_eq) { + range_after_eq += strspn(range_after_eq, WHITESPACE); + return request_parse_range_entries(m, range_after_eq); + } + + range_after_eq = startswith(range, "realtime="); + if (startswith(range, "realtime=")) { + range_after_eq += strspn(range_after_eq, WHITESPACE); + return request_parse_range_time(m, range_after_eq); + } + + return 0; +} + static mhd_result request_parse_arguments_iterator( void *cls, enum MHD_ValueKind kind, @@ -496,10 +587,15 @@ static int request_handler_entries( if (m->cursor) r = sd_journal_seek_cursor(m->journal, m->cursor); + else if (m->since_set) + r = sd_journal_seek_realtime_usec(m->journal, m->since); else if (m->n_skip >= 0) r = sd_journal_seek_head(m->journal); + else if (m->until_set && m->n_skip < 0) + r = sd_journal_seek_realtime_usec(m->journal, m->until); else if (m->n_skip < 0) r = sd_journal_seek_tail(m->journal); + if (r < 0) return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal."); diff --git a/test/units/testsuite-04.journal-gatewayd.sh b/test/units/testsuite-04.journal-gatewayd.sh index 5755ef1a0ae..1b55bd39dbe 100755 --- a/test/units/testsuite-04.journal-gatewayd.sh +++ b/test/units/testsuite-04.journal-gatewayd.sh @@ -11,10 +11,13 @@ fi TEST_MESSAGE="-= This is a test message $RANDOM =-" TEST_TAG="$(systemd-id128 new)" +BEFORE_TIMESTAMP="$(date +%s)" echo "$TEST_MESSAGE" | systemd-cat -t "$TEST_TAG" +sleep 1 journalctl --sync TEST_CURSOR="$(journalctl -q -t "$TEST_TAG" -n 0 --show-cursor | awk '{ print $3; }')" BOOT_CURSOR="$(journalctl -q -b -n 0 --show-cursor | awk '{ print $3; }')" +AFTER_TIMESTAMP="$(date +%s)" /usr/lib/systemd/systemd-journal-gatewayd --version /usr/lib/systemd/systemd-journal-gatewayd --help @@ -47,6 +50,28 @@ curl -Lfs --header "Accept: application/json" --header "Range: entries=$BOOT_CUR # Check if the specified cursor refers to an existing entry and return just that entry curl -Lfs --header "Accept: application/json" --header "Range: entries=$TEST_CURSOR" http://localhost:19531/entries?discrete | \ jq -se "length == 1 and select(.[].MESSAGE == \"$TEST_MESSAGE\")" +# Check entry is present (resp. absent) when filtering by timestamp +curl -Lfs --header "Range: realtime=$BEFORE_TIMESTAMP:" http://localhost:19531/entries?SYSLOG_IDENTIFIER="$TEST_TAG" | \ + grep -qE " $TEST_TAG\[[0-9]+\]: $TEST_MESSAGE" +curl -Lfs --header "Range: realtime=:$AFTER_TIMESTAMP" http://localhost:19531/entries?SYSLOG_IDENTIFIER="$TEST_TAG" | \ + grep -qE " $TEST_TAG\[[0-9]+\]: $TEST_MESSAGE" +curl -Lfs --header "Accept: application/json" --header "Range: realtime=:$BEFORE_TIMESTAMP" http://localhost:19531/entries?SYSLOG_IDENTIFIER="$TEST_TAG" | \ + jq -se "length == 0" +curl -Lfs --header "Accept: application/json" --header "Range: realtime=$AFTER_TIMESTAMP:" http://localhost:19531/entries?SYSLOG_IDENTIFIER="$TEST_TAG" | \ + jq -se "length == 0" +# Check positive and negative skip when filtering by timestamp +echo "-= This is a second test message =-" | systemd-cat -t "$TEST_TAG" +journalctl --sync +TEST2_CURSOR="$(journalctl -q -t "$TEST_TAG" -n 0 --show-cursor | awk '{ print $3; }')" +echo "-= This is a third test message =-" | systemd-cat -t "$TEST_TAG" +journalctl --sync +sleep 1 +END_TIMESTAMP="$(date +%s)" +curl -Lfs --header "Accept: application/json" --header "Range: realtime=$BEFORE_TIMESTAMP::1:1" http://localhost:19531/entries?SYSLOG_IDENTIFIER="$TEST_TAG" | \ + jq -se "length == 1 and select(.[].__CURSOR == \"$TEST2_CURSOR\")" +curl -Lfs --header "Accept: application/json" --header "Range: realtime=$END_TIMESTAMP::-1:1" http://localhost:19531/entries?SYSLOG_IDENTIFIER="$TEST_TAG" | \ + jq -se "length == 1 and select(.[].__CURSOR == \"$TEST2_CURSOR\")" + # No idea how to properly parse this (jq won't cut it), so let's at least do some sanity checks that every # line is either empty or begins with data: curl -Lfs --header "Accept: text/event-stream" http://localhost:19531/entries | \