aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am4
-rw-r--r--man/sd_journal_get_cursor.xml53
-rw-r--r--man/sd_journal_seek_head.xml7
-rw-r--r--src/journal/browse.html77
-rw-r--r--src/journal/journal-gatewayd.c38
-rw-r--r--src/journal/libsystemd-journal.sym5
-rw-r--r--src/journal/sd-journal.c88
-rw-r--r--src/journal/test-journal-stream.c16
-rw-r--r--src/systemd/sd-journal.h1
9 files changed, 263 insertions, 26 deletions
diff --git a/Makefile.am b/Makefile.am
index 0b3013b44..c23afdf17 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -580,7 +580,8 @@ MANPAGES_ALIAS = \
man/sd_journal_seek_tail.3 \
man/sd_journal_seek_monotonic_usec.3 \
man/sd_journal_seek_realtime_usec.3 \
- man/sd_journal_seek_cursor.3
+ man/sd_journal_seek_cursor.3 \
+ man/sd_journal_test_cursor.3
man/reboot.8: man/halt.8
man/poweroff.8: man/halt.8
@@ -649,6 +650,7 @@ man/sd_journal_seek_tail.3: man/sd_journal_seek_head.3
man/sd_journal_seek_monotonic_usec.3: man/sd_journal_seek_head.3
man/sd_journal_seek_realtime_usec.3: man/sd_journal_seek_head.3
man/sd_journal_seek_cursor.3: man/sd_journal_seek_head.3
+man/sd_journal_test_cursor.3: man/sd_journal_get_cursor.3
XML_FILES = \
${patsubst %.1,%.xml,${patsubst %.3,%.xml,${patsubst %.5,%.xml,${patsubst %.7,%.xml,${patsubst %.8,%.xml,$(MANPAGES)}}}}}
diff --git a/man/sd_journal_get_cursor.xml b/man/sd_journal_get_cursor.xml
index 9e00ef7c8..354168bee 100644
--- a/man/sd_journal_get_cursor.xml
+++ b/man/sd_journal_get_cursor.xml
@@ -44,7 +44,8 @@
<refnamediv>
<refname>sd_journal_get_cursor</refname>
- <refpurpose>Get cursor string for the current journal entry</refpurpose>
+ <refname>sd_journal_test_cursor</refname>
+ <refpurpose>Get cursor string for or test cursor string against the current journal entry</refpurpose>
</refnamediv>
<refsynopsisdiv>
@@ -57,6 +58,12 @@
<paramdef>char ** <parameter>cursor</parameter></paramdef>
</funcprototype>
+ <funcprototype>
+ <funcdef>int <function>sd_journal_test_cursor</function></funcdef>
+ <paramdef>sd_journal* <parameter>j</parameter></paramdef>
+ <paramdef>const char * <parameter>cursor</parameter></paramdef>
+ </funcprototype>
+
</funcsynopsis>
</refsynopsisdiv>
@@ -66,7 +73,7 @@
<para><function>sd_journal_get_cursor()</function>
returns a cursor string for the current journal
entry. A cursor is a serialization of the current
- journal position in text form. The string only
+ journal position formatted as text. The string only
contains printable characters and can be passed around
in text form. The cursor identifies a journal entry
globally and in a stable way and may be used to later
@@ -77,16 +84,33 @@
without the specific entry being available locally
will seek to the next closest (in terms of time)
available entry. The call takes two arguments: a
- journal context object and a pointer to a
- string pointer where the cursor string will be
- placed. The string is allocated via libc <citerefentry><refentrytitle>malloc</refentrytitle><manvolnum>3</manvolnum></citerefentry> and should
- be freed after use with
+ journal context object and a pointer to a string
+ pointer where the cursor string will be placed. The
+ string is allocated via libc
+ <citerefentry><refentrytitle>malloc</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+ and should be freed after use with
<citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
- <para>Note that this function will not work before
+ <para>Note that
+ <function>sd_journal_get_cursor()</function> will not
+ work before
<citerefentry><refentrytitle>sd_journal_next</refentrytitle><manvolnum>3</manvolnum></citerefentry>
- (or related call) has been called at least
- once, in order to position the read pointer at a valid entry.</para>
+ (or related call) has been called at least once, in
+ order to position the read pointer at a valid
+ entry.</para>
+
+ <para><function>sd_journal_test_cursor()</function>
+ may be used to check whether the current position in
+ the journal matches the specified cursor. This is
+ useful since cursor strings do not uniquely identify
+ an entry: the same entry might be referred to by
+ multiple different cursor strings, and hence string
+ comparing cursors is not possible. Use this call to
+ verify after an invocation of
+ <citerefentry><refentrytitle>sd_journal_seek_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+ whether the entry being seeked to was actually found
+ in the journal or the next closest entry was used
+ instead.</para>
</refsect1>
<refsect1>
@@ -94,15 +118,20 @@
<para><function>sd_journal_get_cursor()</function>
returns 0 on success or a negative errno-style error
- code.</para>
+ code. <function>sd_journal_test_cursor()</function>
+ returns positive if the current entry matches the
+ specified cursor, 0 if it doesn't match the specified
+ cursor or a negative errno-style error code on
+ failure.</para>
</refsect1>
<refsect1>
<title>Notes</title>
<para>The <function>sd_journal_get_cursor()</function>
- interface is available as shared library, which can be
- compiled and linked to with the
+ and <function>sd_journal_test_cursor()</function>
+ interfaces are available as shared library, which can
+ be compiled and linked to with the
<literal>libsystemd-journal</literal>
<citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
file.</para>
diff --git a/man/sd_journal_seek_head.xml b/man/sd_journal_seek_head.xml
index f8a4eb1d2..b10f069e8 100644
--- a/man/sd_journal_seek_head.xml
+++ b/man/sd_journal_seek_head.xml
@@ -115,7 +115,12 @@
<para><function>sd_journal_seek_cursor()</function>
seeks to the entry located at the specified cursor
string. For details on cursors see
- <citerefentry><refentrytitle>sd_journal_get_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
+ <citerefentry><refentrytitle>sd_journal_get_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>. If
+ no entry matching the specified cursor is found the
+ call will seek to the next closest entry (in terms of
+ time) instead. To verify whether the newly selected
+ entry actually matches the cursor use
+ <citerefentry><refentrytitle>sd_journal_test_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
<para>Note that these calls do not actually make any
entry the new current entry, this needs to be done in
diff --git a/src/journal/browse.html b/src/journal/browse.html
index 5ceca26a5..068b296da 100644
--- a/src/journal/browse.html
+++ b/src/journal/browse.html
@@ -4,7 +4,7 @@
<title>Journal</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
- div#divlogs {
+ div#divlogs, div#diventry {
font-family: monospace;
font-size: 8pt;
background-color: #ffffff;
@@ -15,6 +15,12 @@
white-space: nowrap;
overflow-x: scroll;
}
+ div#diventry {
+ display: none;
+ }
+ div#divlogs {
+ display: block;
+ }
body {
background-color: #ededed;
color: #313739;
@@ -34,17 +40,41 @@
td.message {
padding-left: 5px;
}
+ td.message > a:link, td.message > a:visited {
+ text-decoration: none;
+ color: #313739;
+ }
td.message-error {
padding-left: 5px;
color: red;
font-weight: bold;
}
+ td.message-error > a:link, td.message-error > a:visited {
+ text-decoration: none;
+ color: red;
+ }
td.message-highlight {
padding-left: 5px;
font-weight: bold;
}
- table#tablelogs {
- border-collapse:collapse;
+ td.message-highlight > a:link, td.message-highlight > a:visited {
+ text-decoration: none;
+ color: #313739;
+ }
+ td > a:hover, td > a:active {
+ text-decoration: underline;
+ color: #c13739;
+ }
+ table#tablelogs, table#tableentry {
+ border-collapse: collapse;
+ }
+ td.field {
+ text-align: right;
+ border-right: 1px dotted lightgrey;
+ padding-right: 5px;
+ }
+ td.data {
+ padding-left: 5px;
}
</style>
</head>
@@ -65,6 +95,8 @@
<div id="showing"></div>
<div id="divlogs"><table id="tablelogs"></table></div>
+ <a name="entry"></a>
+ <div id="diventry"><table id="tableentry"></table></div>
<form>
<input id="head" type="button" value="|&lt;" onclick="entriesLoadHead();"/>
@@ -271,7 +303,7 @@
else if (d.SYSLOG_PID != undefined)
buf += "[" + d.SYSLOG_PID + "]";
- buf += '</td><td class="' + clazz + '">';
+ buf += '</td><td class="' + clazz + '"><a href="#entry" onclick="onMessageClick(\'' + lc + '\');">';
if (d.MESSAGE == null)
buf += "[blob data]";
@@ -280,10 +312,10 @@
else
buf += d.MESSAGE;
- buf += '</td></tr>';
+ buf += '</a></td></tr>';
}
- logs.innerHTML = buf + '</tbody>';
+ logs.innerHTML = '<tbody>' + buf + '</tbody>';
if (fc != null)
first_cursor = fc;
@@ -293,12 +325,41 @@
function entriesMore() {
setNEntries(getNEntries() + 10);
- entriesLoad("");
+ entriesLoad(first_cursor);
}
function entriesLess() {
setNEntries(getNEntries() - 10);
- entriesLoad("");
+ entriesLoad(first_cursor);
+ }
+
+ function onResultMessageClick(event) {
+ if ((event.currentTarget.readyState != 4) ||
+ (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+ return;
+
+ var d = JSON.parse(event.currentTarget.responseText);
+
+ document.getElementById("diventry").style.display = "block";
+
+ entry = document.getElementById("tableentry");
+
+ var buf = "";
+
+ for (var key in d){
+ buf += '<tr><td class="field">' + key + '</td><td class="data">' + d[key] + '</td></tr>';
+ }
+
+ entry.innerHTML = '<tbody>' + buf + '</tbody>';
+ }
+
+ function onMessageClick(t) {
+ var request = new XMLHttpRequest();
+ request.open("GET", "/entries?discrete");
+ request.onreadystatechange = onResultMessageClick;
+ request.setRequestHeader("Accept", "application/json");
+ request.setRequestHeader("Range", "entries=" + t + ":0:1");
+ request.send(null);
}
machineLoad();
diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
index 274ef5ffe..33dda266b 100644
--- a/src/journal/journal-gatewayd.c
+++ b/src/journal/journal-gatewayd.c
@@ -49,6 +49,7 @@ typedef struct RequestMeta {
int argument_parse_error;
bool follow;
+ bool discrete;
} RequestMeta;
static const char* const mime_types[_OUTPUT_MODE_MAX] = {
@@ -205,6 +206,19 @@ static ssize_t request_reader_entries(
return MHD_CONTENT_READER_END_OF_STREAM;
}
+ if (m->discrete) {
+ assert(m->cursor);
+
+ r = sd_journal_test_cursor(m->journal, m->cursor);
+ if (r < 0) {
+ log_error("Failed to test cursor: %s", strerror(-r));
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ if (r == 0)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+
pos -= m->size;
m->delta += m->size;
@@ -380,6 +394,22 @@ static int request_parse_arguments_iterator(
return MHD_YES;
}
+ if (streq(key, "discrete")) {
+ if (isempty(value)) {
+ m->discrete = true;
+ return MHD_YES;
+ }
+
+ r = parse_boolean(value);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+
+ m->discrete = r;
+ return MHD_YES;
+ }
+
p = strjoin(key, "=", strempty(value), NULL);
if (!p) {
m->argument_parse_error = log_oom();
@@ -436,6 +466,14 @@ static int request_handler_entries(
if (request_parse_arguments(m, connection) < 0)
return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
+ if (m->discrete) {
+ if (!m->cursor)
+ return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
+
+ m->n_entries = 1;
+ m->n_entries_set = true;
+ }
+
if (m->cursor)
r = sd_journal_seek_cursor(m->journal, m->cursor);
else if (m->n_skip >= 0)
diff --git a/src/journal/libsystemd-journal.sym b/src/journal/libsystemd-journal.sym
index 7dfae2625..77de862dc 100644
--- a/src/journal/libsystemd-journal.sym
+++ b/src/journal/libsystemd-journal.sym
@@ -75,3 +75,8 @@ LIBSYSTEMD_JOURNAL_190 {
global:
sd_journal_get_usage;
} LIBSYSTEMD_JOURNAL_188;
+
+LIBSYSTEMD_JOURNAL_195 {
+global:
+ sd_journal_test_cursor;
+} LIBSYSTEMD_JOURNAL_190;
diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c
index 9e594a2cf..88b382f4c 100644
--- a/src/journal/sd-journal.c
+++ b/src/journal/sd-journal.c
@@ -951,9 +951,8 @@ _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
}
_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
- char *w;
+ char *w, *state;
size_t l;
- char *state;
unsigned long long seqnum, monotonic, realtime, xor_hash;
bool
seqnum_id_set = false,
@@ -966,7 +965,7 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
if (!j)
return -EINVAL;
- if (!cursor)
+ if (isempty(cursor))
return -EINVAL;
FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
@@ -1057,6 +1056,89 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
return 0;
}
+_public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) {
+ int r;
+ char *w, *state;
+ size_t l;
+ Object *o;
+
+ if (!j)
+ return -EINVAL;
+ if (isempty(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;
+
+ FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
+ _cleanup_free_ char *item = NULL;
+ sd_id128_t id;
+ unsigned long long ll;
+ int k = 0;
+
+ if (l < 2 || w[1] != '=')
+ return -EINVAL;
+
+ item = strndup(w, l);
+ if (!item)
+ return -ENOMEM;
+
+ switch (w[0]) {
+
+ case 's':
+ k = sd_id128_from_string(item+2, &id);
+ if (k < 0)
+ return k;
+ if (!sd_id128_equal(id, j->current_file->header->seqnum_id))
+ return 0;
+ break;
+
+ case 'i':
+ if (sscanf(item+2, "%llx", &ll) != 1)
+ return -EINVAL;
+ if (ll != le64toh(o->entry.seqnum))
+ return 0;
+ break;
+
+ case 'b':
+ k = sd_id128_from_string(item+2, &id);
+ if (k < 0)
+ return k;
+ if (!sd_id128_equal(id, o->entry.boot_id))
+ return 0;
+ break;
+
+ case 'm':
+ if (sscanf(item+2, "%llx", &ll) != 1)
+ return -EINVAL;
+ if (ll != le64toh(o->entry.monotonic))
+ return 0;
+ break;
+
+ case 't':
+ if (sscanf(item+2, "%llx", &ll) != 1)
+ return -EINVAL;
+ if (ll != le64toh(o->entry.realtime))
+ return 0;
+ break;
+
+ case 'x':
+ if (sscanf(item+2, "%llx", &ll) != 1)
+ return -EINVAL;
+ if (ll != le64toh(o->entry.xor_hash))
+ return 0;
+ break;
+ }
+ }
+
+ return 1;
+}
+
+
_public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
if (!j)
return -EINVAL;
diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c
index 707dcc178..caea2b2c6 100644
--- a/src/journal/test-journal-stream.c
+++ b/src/journal/test-journal-stream.c
@@ -39,7 +39,7 @@ static void verify_contents(sd_journal *j, unsigned skip) {
i = 0;
SD_JOURNAL_FOREACH(j) {
const void *d;
- char *k;
+ char *k, *c;
size_t l;
unsigned u;
@@ -61,6 +61,10 @@ static void verify_contents(sd_journal *j, unsigned skip) {
}
free(k);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ free(c);
}
if (skip > 0)
@@ -122,17 +126,27 @@ int main(int argc, char *argv[]) {
SD_JOURNAL_FOREACH_BACKWARDS(j) {
const void *d;
size_t l;
+ char *c;
assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0);
printf("\t%.*s\n", (int) l, (const char*) d);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ free(c);
}
SD_JOURNAL_FOREACH(j) {
const void *d;
size_t l;
+ char *c;
assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0);
printf("\t%.*s\n", (int) l, (const char*) d);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ free(c);
}
sd_journal_flush_matches(j);
diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h
index 6c18c8942..9a2aab923 100644
--- a/src/systemd/sd-journal.h
+++ b/src/systemd/sd-journal.h
@@ -104,6 +104,7 @@ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec);
int sd_journal_seek_cursor(sd_journal *j, const char *cursor);
int sd_journal_get_cursor(sd_journal *j, char **cursor);
+int sd_journal_test_cursor(sd_journal *j, const char *cursor);
int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to);
int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, const sd_id128_t boot_id, uint64_t *from, uint64_t *to);