]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journal-gatewayd: add since/until parameters for /entries
authorSamuel BF <8824801-samuelbf@users.noreply.gitlab.com>
Thu, 5 Oct 2023 19:39:45 +0000 (21:39 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 6 Dec 2023 21:22:27 +0000 (22:22 +0100)
Request with Range header like 'entries=<cursor>:' (with a colon at the end,
invalid syntax per the doc), is now rejected with error 400 Bad Request.

fix #4883

man/systemd-journal-gatewayd.service.xml
src/journal-remote/journal-gatewayd.c
test/units/testsuite-04.journal-gatewayd.sh

index 45adc2a042f49cd9e55268e38158c7e4d31a4c5e..80d3bf0820f1df9d470476009730edb06133733f 100644 (file)
     <para>
       <option>Range: entries=<replaceable>cursor</replaceable>[[:<replaceable>num_skip</replaceable>]:<replaceable>num_entries</replaceable>]</option>
     </para>
+    <para>
+      <option>Range: realtime=[<replaceable>since</replaceable>]:[<replaceable>until</replaceable>][[:<replaceable>num_skip</replaceable>]:<replaceable>num_entries</replaceable>]</option>
+    </para>
 
     <para>where
       <replaceable>cursor</replaceable> is a cursor string,
+      <replaceable>since</replaceable> and <replaceable>until</replaceable> are timestamps (seconds since 1970-01-01 00:00:00 UTC),
       <replaceable>num_skip</replaceable> is an integer,
       <replaceable>num_entries</replaceable> is an unsigned integer.
     </para>
index 09194710cb60e9f5d5d5a553e081f30c9b913fb3..9f0d0ec34af636cceedcceea5a7a7806b7ed4e70 100644 (file)
@@ -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.");
 
index 5755ef1a0ae1f1b92b2227146f6db373b12617bf..1b55bd39dbeda4776e80678d724329bc4ea70ea4 100755 (executable)
@@ -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 | \