diff options
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | man/sd_journal_get_cursor.xml | 53 | ||||
-rw-r--r-- | man/sd_journal_seek_head.xml | 7 | ||||
-rw-r--r-- | src/journal/browse.html | 77 | ||||
-rw-r--r-- | src/journal/journal-gatewayd.c | 38 | ||||
-rw-r--r-- | src/journal/libsystemd-journal.sym | 5 | ||||
-rw-r--r-- | src/journal/sd-journal.c | 88 | ||||
-rw-r--r-- | src/journal/test-journal-stream.c | 16 | ||||
-rw-r--r-- | src/systemd/sd-journal.h | 1 |
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="|<" 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); |