mirror of
https://github.com/morgan9e/systemd
synced 2026-04-15 00:47:10 +09:00
journal-gatewayd: add /boots endpoint (#37574)
Add endpoint for listing boots. Output format mimics `journalctl --list-boots -o json`, so it's a plain array containing index, boot ID and timestamps of the first and last entry. Initial implementation returns boots ordered starting with the current one and doesn't allow any filtering (i.e. equivalent of --lines argument). Fixes: #37573
This commit is contained in:
@@ -151,6 +151,17 @@
|
||||
<para>The following URLs are recognized:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><uri>/boots</uri></term>
|
||||
|
||||
<listitem><para>Returns json-seq (RFC 7464) response containing JSON objects, each one containing
|
||||
boot number, its ID and the timestamps of the first and last message. Boots are returned starting
|
||||
with the latest boot first and use the same schema as
|
||||
<command>journalctl --list-boots --output json</command>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><uri>/browse</uri></term>
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ typedef struct RequestMeta {
|
||||
uint64_t n_entries;
|
||||
bool n_entries_set, since_set, until_set;
|
||||
|
||||
sd_id128_t previous_boot_id;
|
||||
int32_t boot_index;
|
||||
|
||||
FILE *tmp;
|
||||
uint64_t delta, size;
|
||||
|
||||
@@ -886,6 +889,132 @@ static int request_handler_machine(
|
||||
return MHD_queue_response(connection, MHD_HTTP_OK, response);
|
||||
}
|
||||
|
||||
static int output_boot(FILE *f, LogId boot, int boot_display_index) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
int r;
|
||||
|
||||
r = sd_json_build(
|
||||
&json,
|
||||
SD_JSON_BUILD_OBJECT(
|
||||
SD_JSON_BUILD_PAIR_INTEGER("index", boot_display_index),
|
||||
SD_JSON_BUILD_PAIR_ID128("boot_id", boot.id),
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("first_entry", boot.first_usec),
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("last_entry", boot.last_usec)));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_json_variant_dump(json, SD_JSON_FORMAT_SEQ, f, /* prefix= */ NULL);
|
||||
}
|
||||
|
||||
static ssize_t request_reader_boots(
|
||||
void *cls,
|
||||
uint64_t pos,
|
||||
char *buf,
|
||||
size_t max) {
|
||||
|
||||
RequestMeta *m = ASSERT_PTR(cls);
|
||||
int r;
|
||||
|
||||
assert(buf);
|
||||
assert(max > 0);
|
||||
assert(pos >= m->delta);
|
||||
|
||||
pos -= m->delta;
|
||||
|
||||
while (pos >= m->size) {
|
||||
LogId boot;
|
||||
off_t sz;
|
||||
|
||||
/* We're seeking from tail (newest boot) so advance to older. */
|
||||
r = discover_next_id(
|
||||
m->journal,
|
||||
LOG_BOOT_ID,
|
||||
/* boot_id */ SD_ID128_NULL,
|
||||
/* unit = */ NULL,
|
||||
m->previous_boot_id,
|
||||
/* advance_older = */ true,
|
||||
&boot);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to advance boot index: %m");
|
||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||
}
|
||||
if (r == 0)
|
||||
return MHD_CONTENT_READER_END_OF_STREAM;
|
||||
|
||||
pos -= m->size;
|
||||
m->delta += m->size;
|
||||
|
||||
r = request_meta_ensure_tmp(m);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to create temporary file: %m");
|
||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||
}
|
||||
|
||||
r = output_boot(m->tmp, boot, m->boot_index);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to serialize boot: %m");
|
||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||
}
|
||||
|
||||
sz = ftello(m->tmp);
|
||||
if (sz < 0) {
|
||||
log_error_errno(errno, "Failed to retrieve file position: %m");
|
||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||
}
|
||||
|
||||
m->size = (uint64_t) sz;
|
||||
|
||||
m->previous_boot_id = boot.id;
|
||||
m->boot_index -= 1;
|
||||
}
|
||||
|
||||
if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
|
||||
log_error_errno(errno, "Failed to seek to position: %m");
|
||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||
}
|
||||
|
||||
size_t n = MIN(m->size - pos, max);
|
||||
|
||||
errno = 0;
|
||||
size_t k = fread(buf, 1, n, m->tmp);
|
||||
if (k != n) {
|
||||
log_error("Failed to read from file: %s", STRERROR_OR_EOF(errno));
|
||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||
}
|
||||
|
||||
return (ssize_t) k;
|
||||
}
|
||||
|
||||
static int request_handler_boots(
|
||||
struct MHD_Connection *connection,
|
||||
void *connection_cls) {
|
||||
|
||||
_cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL;
|
||||
RequestMeta *m = ASSERT_PTR(connection_cls);
|
||||
int r;
|
||||
|
||||
assert(connection);
|
||||
|
||||
r = open_journal(m);
|
||||
if (r < 0)
|
||||
return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %m");
|
||||
|
||||
m->previous_boot_id = SD_ID128_NULL;
|
||||
m->boot_index = 0;
|
||||
r = sd_journal_seek_tail(m->journal); /* seek to newest */
|
||||
if (r < 0)
|
||||
return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to seek in journal: %m");
|
||||
|
||||
response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_boots, m, NULL);
|
||||
if (!response)
|
||||
return respond_oom(connection);
|
||||
|
||||
if (MHD_add_response_header(response, "Content-Type", "application/json-seq") == MHD_NO)
|
||||
return respond_oom(connection);
|
||||
|
||||
return MHD_queue_response(connection, MHD_HTTP_OK, response);
|
||||
}
|
||||
|
||||
static mhd_result request_handler(
|
||||
void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
@@ -932,6 +1061,9 @@ static mhd_result request_handler(
|
||||
if (streq(url, "/machine"))
|
||||
return request_handler_machine(connection, *connection_cls);
|
||||
|
||||
if (streq(url, "/boots"))
|
||||
return request_handler_boots(connection, *connection_cls);
|
||||
|
||||
return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.");
|
||||
}
|
||||
|
||||
|
||||
@@ -1993,7 +1993,7 @@ static int set_matches_for_discover_id(
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int discover_next_id(
|
||||
int discover_next_id(
|
||||
sd_journal *j,
|
||||
LogIdType type,
|
||||
sd_id128_t boot_id, /* optional, used when type == JOURNAL_{SYSTEM,USER}_UNIT_INVOCATION_ID */
|
||||
|
||||
@@ -81,6 +81,15 @@ void json_escape(
|
||||
size_t l,
|
||||
OutputFlags flags);
|
||||
|
||||
int discover_next_id(
|
||||
sd_journal *j,
|
||||
LogIdType type,
|
||||
sd_id128_t boot_id, /* optional, used when type == JOURNAL_{SYSTEM,USER}_UNIT_INVOCATION_ID */
|
||||
const char *unit, /* mandatory when type == JOURNAL_{SYSTEM,USER}_UNIT_INVOCATION_ID */
|
||||
sd_id128_t previous_id,
|
||||
bool advance_older,
|
||||
LogId *ret);
|
||||
|
||||
int journal_find_log_id(
|
||||
sd_journal *j,
|
||||
LogIdType type,
|
||||
|
||||
@@ -139,6 +139,12 @@ curl -LSfs http://localhost:19531/fields/_TRANSPORT
|
||||
(! curl -LSfs http://localhost:19531/fields)
|
||||
(! curl -LSfs http://localhost:19531/fields/foo-bar-baz)
|
||||
|
||||
# /boots
|
||||
curl -LSfs http://localhost:19531/boots >"$LOG_FILE"
|
||||
jq --seq -s . "$LOG_FILE"
|
||||
LAST_BOOT_ID=$(journalctl --list-boots -ojson -n1 | jq -r '.[0].boot_id')
|
||||
jq --seq -se ".[0] | select(.boot_id == \"$LAST_BOOT_ID\")" "$LOG_FILE"
|
||||
|
||||
systemctl stop systemd-journal-gatewayd.{socket,service}
|
||||
|
||||
if ! command -v openssl >/dev/null; then
|
||||
|
||||
Reference in New Issue
Block a user