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
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)}}}}}
<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>
<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>
<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
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>
<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>
<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
<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;
white-space: nowrap;
overflow-x: scroll;
}
+ div#diventry {
+ display: none;
+ }
+ div#divlogs {
+ display: block;
+ }
body {
background-color: #ededed;
color: #313739;
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>
<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();"/>
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]";
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;
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();
int argument_parse_error;
bool follow;
+ bool discrete;
} RequestMeta;
static const char* const mime_types[_OUTPUT_MODE_MAX] = {
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;
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();
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)
global:
sd_journal_get_usage;
} LIBSYSTEMD_JOURNAL_188;
+
+LIBSYSTEMD_JOURNAL_195 {
+global:
+ sd_journal_test_cursor;
+} LIBSYSTEMD_JOURNAL_190;
}
_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,
if (!j)
return -EINVAL;
- if (!cursor)
+ if (isempty(cursor))
return -EINVAL;
FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
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;
i = 0;
SD_JOURNAL_FOREACH(j) {
const void *d;
- char *k;
+ char *k, *c;
size_t l;
unsigned u;
}
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)
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);
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);