summaryrefslogtreecommitdiff
path: root/src/journal
diff options
context:
space:
mode:
Diffstat (limited to 'src/journal')
-rw-r--r--src/journal/.gitignore2
l---------src/journal/Makefile1
-rw-r--r--src/journal/browse.html544
-rw-r--r--src/journal/cat.c180
-rw-r--r--src/journal/compress.c208
-rw-r--r--src/journal/compress.h35
-rw-r--r--src/journal/coredump.c278
-rw-r--r--src/journal/coredumpctl.c583
-rw-r--r--src/journal/fsprg.c384
-rw-r--r--src/journal/fsprg.h64
-rw-r--r--src/journal/journal-authenticate.c563
-rw-r--r--src/journal/journal-authenticate.h44
-rw-r--r--src/journal/journal-def.h216
-rw-r--r--src/journal/journal-file.c2872
-rw-r--r--src/journal/journal-file.h193
-rw-r--r--src/journal/journal-gatewayd.c916
-rw-r--r--src/journal/journal-internal.h127
-rw-r--r--src/journal/journal-qrcode.c138
-rw-r--r--src/journal/journal-qrcode.h30
-rw-r--r--src/journal/journal-send.c608
-rw-r--r--src/journal/journal-vacuum.c318
-rw-r--r--src/journal/journal-vacuum.h26
-rw-r--r--src/journal/journal-verify.c1163
-rw-r--r--src/journal/journal-verify.h26
-rw-r--r--src/journal/journalctl.c1060
-rw-r--r--src/journal/journald-console.c86
-rw-r--r--src/journal/journald-console.h26
-rw-r--r--src/journal/journald-gperf.gperf39
-rw-r--r--src/journal/journald-kmsg.c438
-rw-r--r--src/journal/journald-kmsg.h32
-rw-r--r--src/journal/journald-native.c420
-rw-r--r--src/journal/journald-native.h30
-rw-r--r--src/journal/journald-rate-limit.c275
-rw-r--r--src/journal/journald-rate-limit.h31
-rw-r--r--src/journal/journald-server.c1502
-rw-r--r--src/journal/journald-server.h152
-rw-r--r--src/journal/journald-stream.c459
-rw-r--r--src/journal/journald-stream.h30
-rw-r--r--src/journal/journald-syslog.c492
-rw-r--r--src/journal/journald-syslog.h36
-rw-r--r--src/journal/journald.c140
-rw-r--r--src/journal/journald.conf32
-rw-r--r--src/journal/libsystemd-journal.pc.in19
-rw-r--r--src/journal/libsystemd-journal.sym87
-rw-r--r--src/journal/lookup3.c1009
-rw-r--r--src/journal/lookup3.h22
-rw-r--r--src/journal/mmap-cache.c577
-rw-r--r--src/journal/mmap-cache.h36
-rw-r--r--src/journal/sd-journal.c2391
-rw-r--r--src/journal/test-journal-enum.c53
-rw-r--r--src/journal/test-journal-match.c67
-rw-r--r--src/journal/test-journal-send.c78
-rw-r--r--src/journal/test-journal-stream.c185
-rw-r--r--src/journal/test-journal-syslog.c44
-rw-r--r--src/journal/test-journal-verify.c147
-rw-r--r--src/journal/test-journal.c129
-rw-r--r--src/journal/test-mmap-cache.c79
57 files changed, 19722 insertions, 0 deletions
diff --git a/src/journal/.gitignore b/src/journal/.gitignore
new file mode 100644
index 0000000000..d6a79460cd
--- /dev/null
+++ b/src/journal/.gitignore
@@ -0,0 +1,2 @@
+/journald-gperf.c
+/libsystemd-journal.pc
diff --git a/src/journal/Makefile b/src/journal/Makefile
new file mode 120000
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/journal/Makefile
@@ -0,0 +1 @@
+../Makefile \ No newline at end of file
diff --git a/src/journal/browse.html b/src/journal/browse.html
new file mode 100644
index 0000000000..3594f70c87
--- /dev/null
+++ b/src/journal/browse.html
@@ -0,0 +1,544 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Journal</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <style type="text/css">
+ div#divlogs, div#diventry {
+ font-family: monospace;
+ font-size: 7pt;
+ background-color: #ffffff;
+ padding: 1em;
+ margin: 2em 0em;
+ border-radius: 10px 10px 10px 10px;
+ border: 1px solid threedshadow;
+ white-space: nowrap;
+ overflow-x: scroll;
+ }
+ div#diventry {
+ display: none;
+ }
+ div#divlogs {
+ display: block;
+ }
+ body {
+ background-color: #ededed;
+ color: #313739;
+ font: message-box;
+ margin: 3em;
+ }
+ td.timestamp {
+ text-align: right;
+ border-right: 1px dotted lightgrey;
+ padding-right: 5px;
+ }
+ td.process {
+ border-right: 1px dotted lightgrey;
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+ 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;
+ }
+ 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;
+ }
+ div#keynav {
+ text-align: center;
+ font-size: 7pt;
+ color: #818789;
+ padding-top: 2em;
+ }
+ span.key {
+ font-weight: bold;
+ color: #313739;
+ }
+ div#buttonnav {
+ text-align: center;
+ }
+ button {
+ font-size: 18pt;
+ font-weight: bold;
+ width: 2em;
+ height: 2em;
+ }
+ div#filternav {
+ text-align: center;
+ }
+ select {
+ width: 50em;
+ }
+ </style>
+</head>
+
+<body>
+ <!-- TODO:
+ - live display
+ - show red lines for reboots -->
+
+ <h1 id="title"></h1>
+
+ <div id="os"></div>
+ <div id="virtualization"></div>
+ <div id="cutoff"></div>
+ <div id="machine"></div>
+ <div id="usage"></div>
+ <div id="showing"></div>
+
+ <div id="filternav">
+ <select id="filter" onchange="onFilterChange(this);" onfocus="onFilterFocus(this);">
+ <option>No filter</option>
+ </select>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <input id="boot" type="checkbox" onchange="onBootChange(this);">Only current boot</input>
+ </div>
+
+ <div id="divlogs"><table id="tablelogs"></table></div>
+ <a name="entry"></a>
+ <div id="diventry"><table id="tableentry"></table></div>
+
+ <div id="buttonnav">
+ <button id="head" onclick="entriesLoadHead();" title="First Page">&#8676;</button>
+ <button id="previous" type="button" onclick="entriesLoadPrevious();" title="Previous Page"/>&#8592;</button>
+ <button id="next" type="button" onclick="entriesLoadNext();" title="Next Page"/>&#8594;</button>
+ <button id="tail" type="button" onclick="entriesLoadTail();" title="Last Page"/>&#8677;</button>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <button id="more" type="button" onclick="entriesMore();" title="More Entries"/>+</button>
+ <button id="less" type="button" onclick="entriesLess();" title="Fewer Entries"/>-</button>
+ </div>
+
+ <div id="keynav">
+ <span class="key">g</span>: First Page &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">&#8592;, k, BACKSPACE</span>: Previous Page &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">&#8594;, j, SPACE</span>: Next Page &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">G</span>: Last Page &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">+</span>: More entries &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">-</span>: Fewer entries
+ </div>
+
+ <script type="text/javascript">
+ var first_cursor = null;
+ var last_cursor = null;
+
+ function getNEntries() {
+ var n;
+ n = localStorage["n_entries"];
+ if (n == null)
+ return 50;
+ n = parseInt(n);
+ if (n < 10)
+ return 10;
+ if (n > 1000)
+ return 1000;
+ return n;
+ }
+
+ function showNEntries(n) {
+ var showing = document.getElementById("showing");
+ showing.innerHTML = "Showing <b>" + n.toString() + "</b> entries.";
+ }
+
+ function setNEntries(n) {
+ if (n < 10)
+ return 10;
+ if (n > 1000)
+ return 1000;
+ localStorage["n_entries"] = n.toString();
+ showNEntries(n);
+ }
+
+ function machineLoad() {
+ var request = new XMLHttpRequest();
+ request.open("GET", "/machine");
+ request.onreadystatechange = machineOnResult;
+ request.setRequestHeader("Accept", "application/json");
+ request.send(null);
+ }
+
+ function formatBytes(u) {
+ if (u >= 1024*1024*1024*1024)
+ return (u/1024/1024/1024/1024).toFixed(1) + " TiB";
+ else if (u >= 1024*1024*1024)
+ return (u/1024/1024/1024).toFixed(1) + " GiB";
+ else if (u >= 1024*1024)
+ return (u/1024/1024).toFixed(1) + " MiB";
+ else if (u >= 1024)
+ return (u/1024).toFixed(1) + " KiB";
+ else
+ return u.toString() + " B";
+ }
+
+ function escapeHTML(s) {
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+ }
+
+ function machineOnResult(event) {
+ if ((event.currentTarget.readyState != 4) ||
+ (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+ return;
+
+ var d = JSON.parse(event.currentTarget.responseText);
+
+ var title = document.getElementById("title");
+ title.innerHTML = 'Journal of ' + escapeHTML(d.hostname);
+ document.title = 'Journal of ' + escapeHTML(d.hostname);
+
+ var machine = document.getElementById("machine");
+ machine.innerHTML = 'Machine ID is <b>' + d.machine_id + '</b>, current boot ID is <b>' + d.boot_id + '</b>.';
+
+ var cutoff = document.getElementById("cutoff");
+ var from = new Date(parseInt(d.cutoff_from_realtime) / 1000);
+ var to = new Date(parseInt(d.cutoff_to_realtime) / 1000);
+ cutoff.innerHTML = 'Journal begins at <b>' + from.toLocaleString() + '</b> and ends at <b>' + to.toLocaleString() + '</b>.';
+
+ var usage = document.getElementById("usage");
+ usage.innerHTML = 'Disk usage is <b>' + formatBytes(parseInt(d.usage)) + '</b>.';
+
+ var os = document.getElementById("os");
+ os.innerHTML = 'Operating system is <b>' + escapeHTML(d.os_pretty_name) + '</b>.';
+
+ var virtualization = document.getElementById("virtualization");
+ virtualization.innerHTML = d.virtualization == "bare" ? "Running on <b>bare metal</b>." : "Running on virtualization <b>" + escapeHTML(d.virtualization) + "</b>.";
+ }
+
+ function entriesLoad(range) {
+
+ if (range == null)
+ range = localStorage["cursor"] + ":0";
+ if (range == null)
+ range = "";
+
+ var url = "/entries";
+
+ if (localStorage["filter"] != "" && localStorage["filter"] != null) {
+ url += "?_SYSTEMD_UNIT=" + escape(localStorage["filter"]);
+
+ if (localStorage["boot"] == "1")
+ url += "&boot";
+ } else {
+ if (localStorage["boot"] == "1")
+ url += "?boot";
+ }
+
+ var request = new XMLHttpRequest();
+ request.open("GET", url);
+ request.onreadystatechange = entriesOnResult;
+ request.setRequestHeader("Accept", "application/json");
+ request.setRequestHeader("Range", "entries=" + range + ":" + getNEntries().toString());
+ request.send(null);
+ }
+
+ function entriesLoadNext() {
+ if (last_cursor == null)
+ entriesLoad("");
+ else
+ entriesLoad(last_cursor + ":1");
+ }
+
+ function entriesLoadPrevious() {
+ if (first_cursor == null)
+ entriesLoad("");
+ else
+ entriesLoad(first_cursor + ":-" + getNEntries().toString());
+ }
+
+ function entriesLoadHead() {
+ entriesLoad("");
+ }
+
+ function entriesLoadTail() {
+ entriesLoad(":-" + getNEntries().toString());
+ }
+
+ function entriesOnResult(event) {
+
+ if ((event.currentTarget.readyState != 4) ||
+ (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+ return;
+
+ var logs = document.getElementById("tablelogs");
+
+ var lc = null;
+ var fc = null;
+
+ var i, l = event.currentTarget.responseText.split('\n');
+
+ if (l.length <= 1) {
+ logs.innerHTML = '<tbody><tr><td colspan="3"><i>No further entries...</i></td></tr></tbody>';
+ return;
+ }
+
+ var buf = '';
+
+ for (i in l) {
+
+ if (l[i] == '')
+ continue;
+
+ var d = JSON.parse(l[i]);
+ if (d.MESSAGE == undefined || d.__CURSOR == undefined)
+ continue;
+
+ if (fc == null)
+ fc = d.__CURSOR;
+ lc = d.__CURSOR;
+
+ var priority;
+ if (d.PRIORITY != undefined)
+ priority = parseInt(d.PRIORITY);
+ else
+ priority = 6;
+
+ if (priority <= 3)
+ clazz = "message-error";
+ else if (priority <= 5)
+ clazz = "message-highlight";
+ else
+ clazz = "message";
+
+ buf += '<tr><td class="timestamp">';
+
+ if (d.__REALTIME_TIMESTAMP != undefined) {
+ var timestamp = new Date(parseInt(d.__REALTIME_TIMESTAMP) / 1000);
+ buf += timestamp.toLocaleString();
+ }
+
+ buf += '</td><td class="process">';
+
+ if (d.SYSLOG_IDENTIFIER != undefined)
+ buf += escapeHTML(d.SYSLOG_IDENTIFIER);
+ else if (d._COMM != undefined)
+ buf += escapeHTML(d._COMM);
+
+ if (d._PID != undefined)
+ buf += "[" + escapeHTML(d._PID) + "]";
+ else if (d.SYSLOG_PID != undefined)
+ buf += "[" + escapeHTML(d.SYSLOG_PID) + "]";
+
+ buf += '</td><td class="' + clazz + '"><a href="#entry" onclick="onMessageClick(\'' + d.__CURSOR + '\');">';
+
+ if (d.MESSAGE == null)
+ buf += "[blob data]";
+ else if (d.MESSAGE instanceof Array)
+ buf += "[" + formatBytes(d.MESSAGE.length) + " blob data]";
+ else
+ buf += escapeHTML(d.MESSAGE);
+
+ buf += '</a></td></tr>';
+ }
+
+ logs.innerHTML = '<tbody>' + buf + '</tbody>';
+
+ if (fc != null) {
+ first_cursor = fc;
+ localStorage["cursor"] = fc;
+ }
+ if (lc != null)
+ last_cursor = lc;
+ }
+
+ function entriesMore() {
+ setNEntries(getNEntries() + 10);
+ entriesLoad(first_cursor);
+ }
+
+ function entriesLess() {
+ setNEntries(getNEntries() - 10);
+ 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){
+ var data = d[key];
+
+ if (data == null)
+ data = "[blob data]";
+ else if (data instanceof Array)
+ data = "[" + formatBytes(data.length) + " blob data]";
+ else
+ data = escapeHTML(data);
+
+ buf += '<tr><td class="field">' + key + '</td><td class="data">' + data + '</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);
+ }
+
+ function onKeyUp(event) {
+ switch (event.keyCode) {
+ case 8:
+ case 37:
+ case 75:
+ entriesLoadPrevious();
+ break;
+ case 32:
+ case 39:
+ case 74:
+ entriesLoadNext();
+ break;
+
+ case 71:
+ if (event.shiftKey)
+ entriesLoadTail();
+ else
+ entriesLoadHead();
+ break;
+ case 171:
+ entriesMore();
+ break;
+ case 173:
+ entriesLess();
+ break;
+ }
+ }
+
+ function onMouseWheel(event) {
+ if (event.detail < 0 || event.wheelDelta > 0)
+ entriesLoadPrevious();
+ else
+ entriesLoadNext();
+ }
+
+ function onResultFilterFocus(event) {
+ if ((event.currentTarget.readyState != 4) ||
+ (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+ return;
+
+ f = document.getElementById("filter");
+
+ var l = event.currentTarget.responseText.split('\n');
+ var buf = '<option>No filter</option>';
+ var j = -1;
+
+ for (i in l) {
+
+ if (l[i] == '')
+ continue;
+
+ var d = JSON.parse(l[i]);
+ if (d._SYSTEMD_UNIT == undefined)
+ continue;
+
+ buf += '<option value="' + escape(d._SYSTEMD_UNIT) + '">' + escapeHTML(d._SYSTEMD_UNIT) + '</option>';
+
+ if (d._SYSTEMD_UNIT == localStorage["filter"])
+ j = i;
+ }
+
+ if (j < 0) {
+ if (localStorage["filter"] != null && localStorage["filter"] != "") {
+ buf += '<option value="' + escape(localStorage["filter"]) + '">' + escapeHTML(localStorage["filter"]) + '</option>';
+ j = i + 1;
+ } else
+ j = 0;
+ }
+
+ f.innerHTML = buf;
+ f.selectedIndex = j;
+ }
+
+ function onFilterFocus(w) {
+ var request = new XMLHttpRequest();
+ request.open("GET", "/fields/_SYSTEMD_UNIT");
+ request.onreadystatechange = onResultFilterFocus;
+ request.setRequestHeader("Accept", "application/json");
+ request.send(null);
+ }
+
+ function onFilterChange(w) {
+ if (w.selectedIndex <= 0)
+ localStorage["filter"] = "";
+ else
+ localStorage["filter"] = unescape(w.options[w.selectedIndex].value);
+
+ entriesLoadHead();
+ }
+
+ function onBootChange(w) {
+ localStorage["boot"] = w.checked ? "1" : "0";
+ entriesLoadHead();
+ }
+
+ function initFilter() {
+ f = document.getElementById("filter");
+
+ var buf = '<option>No filter</option>';
+
+ var filter = localStorage["filter"];
+ if (filter != null && filter != "") {
+ buf += '<option value="' + escape(filter) + '">' + escapeHTML(filter) + '</option>';
+ j = 1;
+ } else
+ j = 0;
+
+ f.innerHTML = buf;
+ f.selectedIndex = j;
+ }
+
+ function installHandlers() {
+ document.onkeyup = onKeyUp;
+
+ logs = document.getElementById("divlogs");
+ logs.addEventListener("mousewheel", onMouseWheel, false);
+ logs.addEventListener("DOMMouseScroll", onMouseWheel, false);
+ }
+
+ machineLoad();
+ entriesLoad(null);
+ showNEntries(getNEntries());
+ initFilter();
+ installHandlers();
+ </script>
+</body>
+</html>
diff --git a/src/journal/cat.c b/src/journal/cat.c
new file mode 100644
index 0000000000..523a7a2eda
--- /dev/null
+++ b/src/journal/cat.c
@@ -0,0 +1,180 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <getopt.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+
+#include <systemd/sd-journal.h>
+
+#include "util.h"
+#include "build.h"
+
+static char *arg_identifier = NULL;
+static int arg_priority = LOG_INFO;
+static bool arg_level_prefix = true;
+
+static int help(void) {
+
+ printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+ "Execute process with stdout/stderr connected to the journal.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -t --identifier=STRING Set syslog identifier\n"
+ " -p --priority=PRIORITY Set priority value (0..7)\n"
+ " --level-prefix=BOOL Control whether level prefix shall be parsed\n",
+ program_invocation_short_name);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_LEVEL_PREFIX
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "identifier", required_argument, NULL, 't' },
+ { "priority", required_argument, NULL, 'p' },
+ { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX },
+ { NULL, 0, NULL, 0 }
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0) {
+
+ switch (c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ puts(PACKAGE_STRING);
+ puts(DISTRIBUTION);
+ puts(SYSTEMD_FEATURES);
+ return 0;
+
+ case 't':
+ free(arg_identifier);
+ if (isempty(optarg))
+ arg_identifier = NULL;
+ else {
+ arg_identifier = strdup(optarg);
+ if (!arg_identifier)
+ return log_oom();
+ }
+ break;
+
+ case 'p':
+ arg_priority = log_level_from_string(optarg);
+ if (arg_priority < 0) {
+ log_error("Failed to parse priority value.");
+ return arg_priority;
+ }
+ break;
+
+ case ARG_LEVEL_PREFIX: {
+ int k;
+
+ k = parse_boolean(optarg);
+ if (k < 0) {
+ log_error("Failed to parse level prefix value.");
+ return k;
+ }
+ arg_level_prefix = k;
+ break;
+ }
+
+ default:
+ log_error("Unknown option code %c", c);
+ return -EINVAL;
+ }
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ int r, fd = -1, saved_stderr = -1;
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ fd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix);
+ if (fd < 0) {
+ log_error("Failed to create stream fd: %s", strerror(-fd));
+ r = fd;
+ goto finish;
+ }
+
+ saved_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3);
+
+ if (dup3(fd, STDOUT_FILENO, 0) < 0 ||
+ dup3(fd, STDERR_FILENO, 0) < 0) {
+ log_error("Failed to duplicate fd: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ if (fd >= 3)
+ close_nointr_nofail(fd);
+
+ fd = -1;
+
+ if (argc <= optind)
+ execl("/bin/cat", "/bin/cat", NULL);
+ else
+ execvp(argv[optind], argv + optind);
+
+ r = -errno;
+
+ /* Let's try to restore a working stderr, so we can print the error message */
+ if (saved_stderr >= 0)
+ dup3(saved_stderr, STDERR_FILENO, 0);
+
+ log_error("Failed to execute process: %s", strerror(-r));
+
+finish:
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ if (saved_stderr >= 0)
+ close_nointr_nofail(saved_stderr);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/journal/compress.c b/src/journal/compress.c
new file mode 100644
index 0000000000..75e70c5ffa
--- /dev/null
+++ b/src/journal/compress.c
@@ -0,0 +1,208 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <lzma.h>
+
+#include "compress.h"
+
+bool compress_blob(const void *src, uint64_t src_size, void *dst, uint64_t *dst_size) {
+ lzma_stream s = LZMA_STREAM_INIT;
+ lzma_ret ret;
+ bool b = false;
+
+ assert(src);
+ assert(src_size > 0);
+ assert(dst);
+ assert(dst_size);
+
+ /* Returns false if we couldn't compress the data or the
+ * compressed result is longer than the original */
+
+ ret = lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_NONE);
+ if (ret != LZMA_OK)
+ return false;
+
+ s.next_in = src;
+ s.avail_in = src_size;
+ s.next_out = dst;
+ s.avail_out = src_size;
+
+ /* Does it fit? */
+ if (lzma_code(&s, LZMA_FINISH) != LZMA_STREAM_END)
+ goto fail;
+
+ /* Is it actually shorter? */
+ if (s.avail_out == 0)
+ goto fail;
+
+ *dst_size = src_size - s.avail_out;
+ b = true;
+
+fail:
+ lzma_end(&s);
+
+ return b;
+}
+
+bool uncompress_blob(const void *src, uint64_t src_size,
+ void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size) {
+
+ lzma_stream s = LZMA_STREAM_INIT;
+ lzma_ret ret;
+ bool b = false;
+
+ assert(src);
+ assert(src_size > 0);
+ assert(dst);
+ assert(dst_alloc_size);
+ assert(dst_size);
+ assert(*dst_alloc_size == 0 || *dst);
+
+ ret = lzma_stream_decoder(&s, UINT64_MAX, 0);
+ if (ret != LZMA_OK)
+ return false;
+
+ if (*dst_alloc_size <= src_size) {
+ void *p;
+
+ p = realloc(*dst, src_size*2);
+ if (!p)
+ return false;
+
+ *dst = p;
+ *dst_alloc_size = src_size*2;
+ }
+
+ s.next_in = src;
+ s.avail_in = src_size;
+
+ s.next_out = *dst;
+ s.avail_out = *dst_alloc_size;
+
+ for (;;) {
+ void *p;
+
+ ret = lzma_code(&s, LZMA_FINISH);
+
+ if (ret == LZMA_STREAM_END)
+ break;
+
+ if (ret != LZMA_OK)
+ goto fail;
+
+ p = realloc(*dst, *dst_alloc_size*2);
+ if (!p)
+ goto fail;
+
+ s.next_out = (uint8_t*) p + ((uint8_t*) s.next_out - (uint8_t*) *dst);
+ s.avail_out += *dst_alloc_size;
+
+ *dst = p;
+ *dst_alloc_size *= 2;
+ }
+
+ *dst_size = *dst_alloc_size - s.avail_out;
+ b = true;
+
+fail:
+ lzma_end(&s);
+
+ return b;
+}
+
+bool uncompress_startswith(const void *src, uint64_t src_size,
+ void **buffer, uint64_t *buffer_size,
+ const void *prefix, uint64_t prefix_len,
+ uint8_t extra) {
+
+ lzma_stream s = LZMA_STREAM_INIT;
+ lzma_ret ret;
+ bool b = false;
+
+ /* Checks whether the uncompressed blob starts with the
+ * mentioned prefix. The byte extra needs to follow the
+ * prefix */
+
+ assert(src);
+ assert(src_size > 0);
+ assert(buffer);
+ assert(buffer_size);
+ assert(prefix);
+ assert(*buffer_size == 0 || *buffer);
+
+ ret = lzma_stream_decoder(&s, UINT64_MAX, 0);
+ if (ret != LZMA_OK)
+ return false;
+
+ if (*buffer_size <= prefix_len) {
+ void *p;
+
+ p = realloc(*buffer, prefix_len*2);
+ if (!p)
+ return false;
+
+ *buffer = p;
+ *buffer_size = prefix_len*2;
+ }
+
+ s.next_in = src;
+ s.avail_in = src_size;
+
+ s.next_out = *buffer;
+ s.avail_out = *buffer_size;
+
+ for (;;) {
+ void *p;
+
+ ret = lzma_code(&s, LZMA_FINISH);
+
+ if (ret != LZMA_STREAM_END && ret != LZMA_OK)
+ goto fail;
+
+ if ((*buffer_size - s.avail_out > prefix_len) &&
+ memcmp(*buffer, prefix, prefix_len) == 0 &&
+ ((const uint8_t*) *buffer)[prefix_len] == extra)
+ break;
+
+ if (ret == LZMA_STREAM_END)
+ goto fail;
+
+ p = realloc(*buffer, *buffer_size*2);
+ if (!p)
+ goto fail;
+
+ s.next_out = (uint8_t*) p + ((uint8_t*) s.next_out - (uint8_t*) *buffer);
+ s.avail_out += *buffer_size;
+
+ *buffer = p;
+ *buffer_size *= 2;
+ }
+
+ b = true;
+
+fail:
+ lzma_end(&s);
+
+ return b;
+}
diff --git a/src/journal/compress.h b/src/journal/compress.h
new file mode 100644
index 0000000000..b6f1aa0ed7
--- /dev/null
+++ b/src/journal/compress.h
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+bool compress_blob(const void *src, uint64_t src_size, void *dst, uint64_t *dst_size);
+
+bool uncompress_blob(const void *src, uint64_t src_size,
+ void **dst, uint64_t *dst_alloc_size, uint64_t* dst_size);
+
+bool uncompress_startswith(const void *src, uint64_t src_size,
+ void **buffer, uint64_t *buffer_size,
+ const void *prefix, uint64_t prefix_len,
+ uint8_t extra);
diff --git a/src/journal/coredump.c b/src/journal/coredump.c
new file mode 100644
index 0000000000..a507fc65f8
--- /dev/null
+++ b/src/journal/coredump.c
@@ -0,0 +1,278 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+
+#include <systemd/sd-journal.h>
+
+#ifdef HAVE_LOGIND
+#include <systemd/sd-login.h>
+#endif
+
+#include "log.h"
+#include "util.h"
+#include "mkdir.h"
+#include "special.h"
+#include "cgroup-util.h"
+
+#define COREDUMP_MAX (24*1024*1024)
+
+enum {
+ ARG_PID = 1,
+ ARG_UID,
+ ARG_GID,
+ ARG_SIGNAL,
+ ARG_TIMESTAMP,
+ ARG_COMM,
+ _ARG_MAX
+};
+
+static int divert_coredump(void) {
+ FILE *f;
+ int r;
+
+ log_info("Detected coredump of the journal daemon itself, diverting coredump to /var/lib/systemd/coredump/.");
+
+ mkdir_p_label("/var/lib/systemd/coredump", 0755);
+
+ f = fopen("/var/lib/systemd/coredump/core.systemd-journald", "we");
+ if (!f) {
+ log_error("Failed to create coredump file: %m");
+ return -errno;
+ }
+
+ for (;;) {
+ uint8_t buffer[4096];
+ size_t l, q;
+
+ l = fread(buffer, 1, sizeof(buffer), stdin);
+ if (l <= 0) {
+ if (ferror(f)) {
+ log_error("Failed to read coredump: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ r = 0;
+ break;
+ }
+
+ q = fwrite(buffer, 1, l, f);
+ if (q != l) {
+ log_error("Failed to write coredump: %m");
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ fflush(f);
+
+ if (ferror(f)) {
+ log_error("Failed to write coredump: %m");
+ r = -errno;
+ }
+
+finish:
+ fclose(f);
+ return r;
+}
+
+int main(int argc, char* argv[]) {
+ int r, j = 0;
+ char *p = NULL;
+ ssize_t n;
+ pid_t pid;
+ uid_t uid;
+ gid_t gid;
+ struct iovec iovec[14];
+ char *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL,
+ *core_timestamp = NULL, *core_comm = NULL, *core_exe = NULL, *core_unit = NULL,
+ *core_session = NULL, *core_message = NULL, *core_cmdline = NULL, *t;
+
+ prctl(PR_SET_DUMPABLE, 0);
+
+ if (argc != _ARG_MAX) {
+ log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+ log_open();
+
+ log_error("Invalid number of arguments passed from kernel.");
+ r = -EINVAL;
+ goto finish;
+ }
+
+ r = parse_pid(argv[ARG_PID], &pid);
+ if (r < 0) {
+ log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+ log_open();
+
+ log_error("Failed to parse PID.");
+ goto finish;
+ }
+
+ if (cg_pid_get_unit(pid, &t) >= 0) {
+
+ if (streq(t, SPECIAL_JOURNALD_SERVICE)) {
+ /* Make sure we don't make use of the journal,
+ * if it's the journal which is crashing */
+ log_set_target(LOG_TARGET_KMSG);
+ log_open();
+
+ r = divert_coredump();
+ goto finish;
+ }
+
+ core_unit = strappend("COREDUMP_UNIT=", t);
+ free(t);
+
+ if (core_unit)
+ IOVEC_SET_STRING(iovec[j++], core_unit);
+ }
+
+ /* OK, now we know it's not the journal, hence make use of
+ * it */
+ log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+ log_open();
+
+ r = parse_uid(argv[ARG_UID], &uid);
+ if (r < 0) {
+ log_error("Failed to parse UID.");
+ goto finish;
+ }
+
+ r = parse_gid(argv[ARG_GID], &gid);
+ if (r < 0) {
+ log_error("Failed to parse GID.");
+ goto finish;
+ }
+
+ core_pid = strappend("COREDUMP_PID=", argv[ARG_PID]);
+ if (core_pid)
+ IOVEC_SET_STRING(iovec[j++], core_pid);
+
+ core_uid = strappend("COREDUMP_UID=", argv[ARG_UID]);
+ if (core_uid)
+ IOVEC_SET_STRING(iovec[j++], core_uid);
+
+ core_gid = strappend("COREDUMP_GID=", argv[ARG_GID]);
+ if (core_gid)
+ IOVEC_SET_STRING(iovec[j++], core_gid);
+
+ core_signal = strappend("COREDUMP_SIGNAL=", argv[ARG_SIGNAL]);
+ if (core_signal)
+ IOVEC_SET_STRING(iovec[j++], core_signal);
+
+ core_comm = strappend("COREDUMP_COMM=", argv[ARG_COMM]);
+ if (core_comm)
+ IOVEC_SET_STRING(iovec[j++], core_comm);
+
+#ifdef HAVE_LOGIND
+ if (sd_pid_get_session(pid, &t) >= 0) {
+ core_session = strappend("COREDUMP_SESSION=", t);
+ free(t);
+
+ if (core_session)
+ IOVEC_SET_STRING(iovec[j++], core_session);
+ }
+
+#endif
+
+ if (get_process_exe(pid, &t) >= 0) {
+ core_exe = strappend("COREDUMP_EXE=", t);
+ free(t);
+
+ if (core_exe)
+ IOVEC_SET_STRING(iovec[j++], core_exe);
+ }
+
+ if (get_process_cmdline(pid, LINE_MAX, false, &t) >= 0) {
+ core_cmdline = strappend("COREDUMP_CMDLINE=", t);
+ free(t);
+
+ if (core_cmdline)
+ IOVEC_SET_STRING(iovec[j++], core_cmdline);
+ }
+
+ core_timestamp = strjoin("COREDUMP_TIMESTAMP=", argv[ARG_TIMESTAMP], "000000", NULL);
+ if (core_timestamp)
+ IOVEC_SET_STRING(iovec[j++], core_timestamp);
+
+ IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
+ IOVEC_SET_STRING(iovec[j++], "PRIORITY=2");
+
+ core_message = strjoin("MESSAGE=Process ", argv[ARG_PID], " (", argv[ARG_COMM], ") dumped core.", NULL);
+ if (core_message)
+ IOVEC_SET_STRING(iovec[j++], core_message);
+
+ /* Now, let's drop privileges to become the user who owns the
+ * segfaulted process and allocate the coredump memory under
+ * his uid. This also ensures that the credentials journald
+ * will see are the ones of the coredumping user, thus making
+ * sure the user himself gets access to the core dump. */
+
+ if (setresgid(gid, gid, gid) < 0 ||
+ setresuid(uid, uid, uid) < 0) {
+ log_error("Failed to drop privileges: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ p = malloc(9 + COREDUMP_MAX);
+ if (!p) {
+ r = log_oom();
+ goto finish;
+ }
+
+ memcpy(p, "COREDUMP=", 9);
+
+ n = loop_read(STDIN_FILENO, p + 9, COREDUMP_MAX, false);
+ if (n < 0) {
+ log_error("Failed to read core dump data: %s", strerror(-n));
+ r = (int) n;
+ goto finish;
+ }
+
+ iovec[j].iov_base = p;
+ iovec[j].iov_len = 9 + n;
+ j++;
+
+ r = sd_journal_sendv(iovec, j);
+ if (r < 0)
+ log_error("Failed to send coredump: %s", strerror(-r));
+
+finish:
+ free(p);
+ free(core_pid);
+ free(core_uid);
+ free(core_gid);
+ free(core_signal);
+ free(core_timestamp);
+ free(core_comm);
+ free(core_exe);
+ free(core_cmdline);
+ free(core_unit);
+ free(core_session);
+ free(core_message);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/journal/coredumpctl.c b/src/journal/coredumpctl.c
new file mode 100644
index 0000000000..4adc9236f1
--- /dev/null
+++ b/src/journal/coredumpctl.c
@@ -0,0 +1,583 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <locale.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <systemd/sd-journal.h>
+
+#include "build.h"
+#include "set.h"
+#include "util.h"
+#include "log.h"
+#include "path-util.h"
+#include "pager.h"
+
+static enum {
+ ACTION_NONE,
+ ACTION_LIST,
+ ACTION_DUMP,
+ ACTION_GDB,
+} arg_action = ACTION_LIST;
+
+static Set *matches = NULL;
+static FILE* output = NULL;
+static char* field = NULL;
+
+static int arg_no_pager = false;
+static int arg_no_legend = false;
+
+static Set *new_matches(void) {
+ Set *set;
+ char *tmp;
+ int r;
+
+ set = set_new(trivial_hash_func, trivial_compare_func);
+ if (!set) {
+ log_oom();
+ return NULL;
+ }
+
+ tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
+ if (!tmp) {
+ log_oom();
+ set_free(set);
+ return NULL;
+ }
+
+ r = set_put(set, tmp);
+ if (r < 0) {
+ log_error("failed to add to set: %s", strerror(-r));
+ free(tmp);
+ set_free(set);
+ return NULL;
+ }
+
+ return set;
+}
+
+static int help(void) {
+ printf("%s [OPTIONS...] [MATCHES...]\n\n"
+ "List or retrieve coredumps from the journal.\n\n"
+ "Flags:\n"
+ " -o --output=FILE Write output to FILE\n"
+ " --no-pager Do not pipe output into a pager\n"
+
+ "Commands:\n"
+ " -h --help Show this help\n"
+ " --version Print version string\n"
+ " -F --field=FIELD List all values a certain field takes\n"
+ " gdb Start gdb for the first matching coredump\n"
+ " list List available coredumps\n"
+ " dump PID Print coredump to stdout\n"
+ " dump PATH Print coredump to stdout\n"
+ , program_invocation_short_name);
+
+ return 0;
+}
+
+static int add_match(Set *set, const char *match) {
+ int r = -ENOMEM;
+ unsigned pid;
+ const char* prefix;
+ char *pattern = NULL;
+ char _cleanup_free_ *p = NULL;
+
+ if (strchr(match, '='))
+ prefix = "";
+ else if (strchr(match, '/')) {
+ p = path_make_absolute_cwd(match);
+ if (!p)
+ goto fail;
+
+ match = p;
+ prefix = "COREDUMP_EXE=";
+ }
+ else if (safe_atou(match, &pid) == 0)
+ prefix = "COREDUMP_PID=";
+ else
+ prefix = "COREDUMP_COMM=";
+
+ pattern = strjoin(prefix, match, NULL);
+ if (!pattern)
+ goto fail;
+
+ r = set_put(set, pattern);
+ if (r < 0) {
+ log_error("failed to add pattern '%s': %s",
+ pattern, strerror(-r));
+ goto fail;
+ }
+ log_debug("Added pattern: %s", pattern);
+
+ return 0;
+fail:
+ free(pattern);
+ log_error("failed to add match: %s", strerror(-r));
+ return r;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ ARG_NO_LEGEND,
+ };
+
+ int r, c;
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version" , no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "output", required_argument, NULL, 'o' },
+ { "field", required_argument, NULL, 'F' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
+ switch(c) {
+ case 'h':
+ help();
+ arg_action = ACTION_NONE;
+ return 0;
+
+ case ARG_VERSION:
+ puts(PACKAGE_STRING);
+ puts(DISTRIBUTION);
+ puts(SYSTEMD_FEATURES);
+ arg_action = ACTION_NONE;
+ return 0;
+
+ case ARG_NO_PAGER:
+ arg_no_pager = true;
+ break;
+
+ case ARG_NO_LEGEND:
+ arg_no_legend = true;
+ break;
+
+ case 'o':
+ if (output) {
+ log_error("cannot set output more than once");
+ return -EINVAL;
+ }
+
+ output = fopen(optarg, "we");
+ if (!output) {
+ log_error("writing to '%s': %m", optarg);
+ return -errno;
+ }
+
+ break;
+
+ case 'F':
+ if (field) {
+ log_error("cannot use --field/-F more than once");
+ return -EINVAL;
+ }
+
+ field = optarg;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ log_error("Unknown option code %c", c);
+ return -EINVAL;
+ }
+
+ if (optind < argc) {
+ const char *cmd = argv[optind++];
+ if(streq(cmd, "list"))
+ arg_action = ACTION_LIST;
+ else if (streq(cmd, "dump"))
+ arg_action = ACTION_DUMP;
+ else if (streq(cmd, "gdb"))
+ arg_action = ACTION_GDB;
+ else {
+ log_error("Unknown action '%s'", cmd);
+ return -EINVAL;
+ }
+ }
+
+ if (field && arg_action != ACTION_LIST) {
+ log_error("Option --field/-F only makes sense with list");
+ return -EINVAL;
+ }
+
+ while (optind < argc) {
+ r = add_match(matches, argv[optind]);
+ if (r != 0)
+ return r;
+ optind++;
+ }
+
+ return 0;
+}
+
+static int retrieve(const void *data,
+ size_t len,
+ const char *name,
+ const char **var) {
+
+ size_t ident;
+
+ ident = strlen(name) + 1; /* name + "=" */
+
+ if (len < ident)
+ return 0;
+
+ if (memcmp(data, name, ident - 1) != 0)
+ return 0;
+
+ if (((const char*) data)[ident - 1] != '=')
+ return 0;
+
+ *var = strndup((const char*)data + ident, len - ident);
+ if (!var)
+ return log_oom();
+
+ return 0;
+}
+
+static void print_field(FILE* file, sd_journal *j) {
+ const char _cleanup_free_ *value = NULL;
+ const void *d;
+ size_t l;
+
+ assert(field);
+
+ SD_JOURNAL_FOREACH_DATA(j, d, l)
+ retrieve(d, l, field, &value);
+ if (value)
+ fprintf(file, "%s\n", value);
+}
+
+static int print_entry(FILE* file, sd_journal *j, int had_legend) {
+ const char _cleanup_free_
+ *pid = NULL, *uid = NULL, *gid = NULL,
+ *sgnl = NULL, *exe = NULL;
+ const void *d;
+ size_t l;
+ usec_t t;
+ char buf[FORMAT_TIMESTAMP_MAX];
+ int r;
+
+ SD_JOURNAL_FOREACH_DATA(j, d, l) {
+ retrieve(d, l, "COREDUMP_PID", &pid);
+ retrieve(d, l, "COREDUMP_PID", &pid);
+ retrieve(d, l, "COREDUMP_UID", &uid);
+ retrieve(d, l, "COREDUMP_GID", &gid);
+ retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
+ retrieve(d, l, "COREDUMP_EXE", &exe);
+ if (!exe)
+ retrieve(d, l, "COREDUMP_COMM", &exe);
+ if (!exe)
+ retrieve(d, l, "COREDUMP_CMDLINE", &exe);
+ }
+
+ if (!pid && !uid && !gid && !sgnl && !exe) {
+ log_warning("Empty coredump log entry");
+ return -EINVAL;
+ }
+
+ r = sd_journal_get_realtime_usec(j, &t);
+ if (r < 0) {
+ log_error("Failed to get realtime timestamp: %s", strerror(-r));
+ return r;
+ }
+
+ format_timestamp(buf, sizeof(buf), t);
+
+ if (!had_legend && !arg_no_legend)
+ fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
+ FORMAT_TIMESTAMP_MAX-1, "TIME",
+ 6, "PID",
+ 5, "UID",
+ 5, "GID",
+ 3, "SIG",
+ "EXE");
+
+ fprintf(file, "%*s %*s %*s %*s %*s %s\n",
+ FORMAT_TIMESTAMP_MAX-1, buf,
+ 6, pid,
+ 5, uid,
+ 5, gid,
+ 3, sgnl,
+ exe);
+
+ return 0;
+}
+
+static int dump_list(sd_journal *j) {
+ int found = 0;
+
+ assert(j);
+
+ SD_JOURNAL_FOREACH(j) {
+ if (field)
+ print_field(stdout, j);
+ else
+ print_entry(stdout, j, found++);
+ }
+
+ if (!field && !found) {
+ log_notice("No coredumps found");
+ return -ESRCH;
+ }
+
+ return 0;
+}
+
+static int focus(sd_journal *j) {
+ int r;
+
+ r = sd_journal_seek_tail(j);
+ if (r == 0)
+ r = sd_journal_previous(j);
+ if (r < 0) {
+ log_error("Failed to search journal: %s", strerror(-r));
+ return r;
+ }
+ if (r == 0) {
+ log_error("No match found");
+ return -ESRCH;
+ }
+ return r;
+}
+
+static int dump_core(sd_journal* j) {
+ const void *data;
+ size_t len, ret;
+ int r;
+
+ assert(j);
+
+ r = focus(j);
+ if (r < 0)
+ return r;
+
+ print_entry(output ? stdout : stderr, j, false);
+
+ if (on_tty() && !output) {
+ log_error("Refusing to dump core to tty");
+ return -ENOTTY;
+ }
+
+ r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
+ if (r < 0) {
+ log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
+ return r;
+ }
+
+ assert(len >= 9);
+ data = (const uint8_t*) data + 9;
+ len -= 9;
+
+ ret = fwrite(data, len, 1, output ? output : stdout);
+ if (ret != 1) {
+ log_error("dumping coredump: %m (%zu)", ret);
+ return -errno;
+ }
+
+ r = sd_journal_previous(j);
+ if (r >= 0)
+ log_warning("More than one entry matches, ignoring rest.\n");
+
+ return 0;
+}
+
+static int run_gdb(sd_journal *j) {
+ char path[] = "/var/tmp/coredump-XXXXXX";
+ const void *data;
+ size_t len;
+ ssize_t sz;
+ pid_t pid;
+ _cleanup_free_ char *exe = NULL;
+ int r;
+ _cleanup_close_ int fd = -1;
+ siginfo_t st;
+
+ assert(j);
+
+ r = focus(j);
+ if (r < 0)
+ return r;
+
+ print_entry(stdout, j, false);
+
+ r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
+ if (r < 0) {
+ log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
+ return r;
+ }
+
+ assert(len >= 13);
+ data = (const uint8_t*) data + 13;
+ len -= 13;
+
+ exe = strndup(data, len);
+ if (!exe)
+ return log_oom();
+
+ if (endswith(exe, " (deleted)")) {
+ log_error("Binary already deleted.");
+ return -ENOENT;
+ }
+
+ r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
+ if (r < 0) {
+ log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
+ return r;
+ }
+
+ assert(len >= 9);
+ data = (const uint8_t*) data + 9;
+ len -= 9;
+
+ fd = mkostemp(path, O_WRONLY);
+ if (fd < 0) {
+ log_error("Failed to create temporary file: %m");
+ return -errno;
+ }
+
+ sz = write(fd, data, len);
+ if (sz < 0) {
+ log_error("Failed to write temporary file: %s", strerror(errno));
+ r = -errno;
+ goto finish;
+ }
+ if (sz != (ssize_t) len) {
+ log_error("Short write to temporary file.");
+ r = -EIO;
+ goto finish;
+ }
+
+ close_nointr_nofail(fd);
+ fd = -1;
+
+ pid = fork();
+ if (pid < 0) {
+ log_error("Failed to fork(): %m");
+ r = -errno;
+ goto finish;
+ }
+ if (pid == 0) {
+ execlp("gdb", "gdb", exe, path, NULL);
+ log_error("Failed to invoke gdb: %m");
+ _exit(1);
+ }
+
+ r = wait_for_terminate(pid, &st);
+ if (r < 0) {
+ log_error("Failed to wait for gdb: %m");
+ goto finish;
+ }
+
+ r = st.si_code == CLD_EXITED ? st.si_status : 255;
+
+finish:
+ unlink(path);
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ sd_journal *j = NULL;
+ const char* match;
+ Iterator it;
+ int r = 0;
+
+ setlocale(LC_ALL, "");
+ log_parse_environment();
+ log_open();
+
+ matches = new_matches();
+ if (!matches) {
+ r = -ENOMEM;
+ goto end;
+ }
+
+ r = parse_argv(argc, argv);
+ if (r < 0)
+ goto end;
+
+ if (arg_action == ACTION_NONE)
+ goto end;
+
+ r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+ if (r < 0) {
+ log_error("Failed to open journal: %s", strerror(-r));
+ goto end;
+ }
+
+ SET_FOREACH(match, matches, it) {
+ r = sd_journal_add_match(j, match, strlen(match));
+ if (r != 0) {
+ log_error("Failed to add match '%s': %s",
+ match, strerror(-r));
+ goto end;
+ }
+ }
+
+ switch(arg_action) {
+
+ case ACTION_LIST:
+ if (!arg_no_pager)
+ pager_open();
+
+ r = dump_list(j);
+ break;
+
+ case ACTION_DUMP:
+ r = dump_core(j);
+ break;
+
+ case ACTION_GDB:
+ r = run_gdb(j);
+ break;
+
+ default:
+ assert_not_reached("Shouldn't be here");
+ }
+
+end:
+ if (j)
+ sd_journal_close(j);
+
+ set_free_free(matches);
+
+ pager_close();
+
+ if (output)
+ fclose(output);
+
+ return r >= 0 ? r : EXIT_FAILURE;
+}
diff --git a/src/journal/fsprg.c b/src/journal/fsprg.c
new file mode 100644
index 0000000000..2190b7c796
--- /dev/null
+++ b/src/journal/fsprg.c
@@ -0,0 +1,384 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/*
+ * fsprg v0.1 - (seekable) forward-secure pseudorandom generator
+ * Copyright (C) 2012 B. Poettering
+ * Contact: fsprg@point-at-infinity.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <gcrypt.h>
+#include <string.h>
+#include <assert.h>
+
+#include "fsprg.h"
+
+#define ISVALID_SECPAR(secpar) (((secpar) % 16 == 0) && ((secpar) >= 16) && ((secpar) <= 16384))
+#define VALIDATE_SECPAR(secpar) assert(ISVALID_SECPAR(secpar));
+
+#define RND_HASH GCRY_MD_SHA256
+#define RND_GEN_P 0x01
+#define RND_GEN_Q 0x02
+#define RND_GEN_X 0x03
+
+/******************************************************************************/
+
+static void mpi_export(void *buf, size_t buflen, const gcry_mpi_t x) {
+ unsigned len;
+ size_t nwritten;
+
+ assert(gcry_mpi_cmp_ui(x, 0) >= 0);
+ len = (gcry_mpi_get_nbits(x) + 7) / 8;
+ assert(len <= buflen);
+ memset(buf, 0, buflen);
+ gcry_mpi_print(GCRYMPI_FMT_USG, buf + (buflen - len), len, &nwritten, x);
+ assert(nwritten == len);
+}
+
+static gcry_mpi_t mpi_import(const void *buf, size_t buflen) {
+ gcry_mpi_t h;
+ unsigned len;
+
+ gcry_mpi_scan(&h, GCRYMPI_FMT_USG, buf, buflen, NULL);
+ len = (gcry_mpi_get_nbits(h) + 7) / 8;
+ assert(len <= buflen);
+ assert(gcry_mpi_cmp_ui(h, 0) >= 0);
+
+ return h;
+}
+
+static void uint64_export(void *buf, size_t buflen, uint64_t x) {
+ assert(buflen == 8);
+ ((uint8_t*) buf)[0] = (x >> 56) & 0xff;
+ ((uint8_t*) buf)[1] = (x >> 48) & 0xff;
+ ((uint8_t*) buf)[2] = (x >> 40) & 0xff;
+ ((uint8_t*) buf)[3] = (x >> 32) & 0xff;
+ ((uint8_t*) buf)[4] = (x >> 24) & 0xff;
+ ((uint8_t*) buf)[5] = (x >> 16) & 0xff;
+ ((uint8_t*) buf)[6] = (x >> 8) & 0xff;
+ ((uint8_t*) buf)[7] = (x >> 0) & 0xff;
+}
+
+static uint64_t uint64_import(const void *buf, size_t buflen) {
+ assert(buflen == 8);
+ return
+ (uint64_t)(((uint8_t*) buf)[0]) << 56 |
+ (uint64_t)(((uint8_t*) buf)[1]) << 48 |
+ (uint64_t)(((uint8_t*) buf)[2]) << 40 |
+ (uint64_t)(((uint8_t*) buf)[3]) << 32 |
+ (uint64_t)(((uint8_t*) buf)[4]) << 24 |
+ (uint64_t)(((uint8_t*) buf)[5]) << 16 |
+ (uint64_t)(((uint8_t*) buf)[6]) << 8 |
+ (uint64_t)(((uint8_t*) buf)[7]) << 0;
+}
+
+/* deterministically generate from seed/idx a string of buflen pseudorandom bytes */
+static void det_randomize(void *buf, size_t buflen, const void *seed, size_t seedlen, uint32_t idx) {
+ gcry_md_hd_t hd, hd2;
+ size_t olen, cpylen;
+ uint32_t ctr;
+
+ olen = gcry_md_get_algo_dlen(RND_HASH);
+ gcry_md_open(&hd, RND_HASH, 0);
+ gcry_md_write(hd, seed, seedlen);
+ gcry_md_putc(hd, (idx >> 24) & 0xff);
+ gcry_md_putc(hd, (idx >> 16) & 0xff);
+ gcry_md_putc(hd, (idx >> 8) & 0xff);
+ gcry_md_putc(hd, (idx >> 0) & 0xff);
+
+ for (ctr = 0; buflen; ctr++) {
+ gcry_md_copy(&hd2, hd);
+ gcry_md_putc(hd2, (ctr >> 24) & 0xff);
+ gcry_md_putc(hd2, (ctr >> 16) & 0xff);
+ gcry_md_putc(hd2, (ctr >> 8) & 0xff);
+ gcry_md_putc(hd2, (ctr >> 0) & 0xff);
+ gcry_md_final(hd2);
+ cpylen = (buflen < olen) ? buflen : olen;
+ memcpy(buf, gcry_md_read(hd2, RND_HASH), cpylen);
+ gcry_md_close(hd2);
+ buf += cpylen;
+ buflen -= cpylen;
+ }
+ gcry_md_close(hd);
+}
+
+/* deterministically generate from seed/idx a prime of length `bits' that is 3 (mod 4) */
+static gcry_mpi_t genprime3mod4(int bits, const void *seed, size_t seedlen, uint32_t idx) {
+ size_t buflen = bits / 8;
+ uint8_t buf[buflen];
+ gcry_mpi_t p;
+
+ assert(bits % 8 == 0);
+ assert(buflen > 0);
+
+ det_randomize(buf, buflen, seed, seedlen, idx);
+ buf[0] |= 0xc0; /* set upper two bits, so that n=pq has maximum size */
+ buf[buflen - 1] |= 0x03; /* set lower two bits, to have result 3 (mod 4) */
+
+ p = mpi_import(buf, buflen);
+ while (gcry_prime_check(p, 0))
+ gcry_mpi_add_ui(p, p, 4);
+
+ return p;
+}
+
+/* deterministically generate from seed/idx a quadratic residue (mod n) */
+static gcry_mpi_t gensquare(const gcry_mpi_t n, const void *seed, size_t seedlen, uint32_t idx, unsigned secpar) {
+ size_t buflen = secpar / 8;
+ uint8_t buf[buflen];
+ gcry_mpi_t x;
+
+ det_randomize(buf, buflen, seed, seedlen, idx);
+ buf[0] &= 0x7f; /* clear upper bit, so that we have x < n */
+ x = mpi_import(buf, buflen);
+ assert(gcry_mpi_cmp(x, n) < 0);
+ gcry_mpi_mulm(x, x, x, n);
+ return x;
+}
+
+/* compute 2^m (mod phi(p)), for a prime p */
+static gcry_mpi_t twopowmodphi(uint64_t m, const gcry_mpi_t p) {
+ gcry_mpi_t phi, r;
+ int n;
+
+ phi = gcry_mpi_new(0);
+ gcry_mpi_sub_ui(phi, p, 1);
+
+ /* count number of used bits in m */
+ for (n = 0; (1ULL << n) <= m; n++)
+ ;
+
+ r = gcry_mpi_new(0);
+ gcry_mpi_set_ui(r, 1);
+ while (n) { /* square and multiply algorithm for fast exponentiation */
+ n--;
+ gcry_mpi_mulm(r, r, r, phi);
+ if (m & ((uint64_t)1 << n)) {
+ gcry_mpi_add(r, r, r);
+ if (gcry_mpi_cmp(r, phi) >= 0)
+ gcry_mpi_sub(r, r, phi);
+ }
+ }
+
+ gcry_mpi_release(phi);
+ return r;
+}
+
+/* Decompose $x \in Z_n$ into $(xp,xq) \in Z_p \times Z_q$ using Chinese Remainder Theorem */
+static void CRT_decompose(gcry_mpi_t *xp, gcry_mpi_t *xq, const gcry_mpi_t x, const gcry_mpi_t p, const gcry_mpi_t q) {
+ *xp = gcry_mpi_new(0);
+ *xq = gcry_mpi_new(0);
+ gcry_mpi_mod(*xp, x, p);
+ gcry_mpi_mod(*xq, x, q);
+}
+
+/* Compose $(xp,xq) \in Z_p \times Z_q$ into $x \in Z_n$ using Chinese Remainder Theorem */
+static void CRT_compose(gcry_mpi_t *x, const gcry_mpi_t xp, const gcry_mpi_t xq, const gcry_mpi_t p, const gcry_mpi_t q) {
+ gcry_mpi_t a, u;
+
+ a = gcry_mpi_new(0);
+ u = gcry_mpi_new(0);
+ *x = gcry_mpi_new(0);
+ gcry_mpi_subm(a, xq, xp, q);
+ gcry_mpi_invm(u, p, q);
+ gcry_mpi_mulm(a, a, u, q); /* a = (xq - xp) / p (mod q) */
+ gcry_mpi_mul(*x, p, a);
+ gcry_mpi_add(*x, *x, xp); /* x = p * ((xq - xp) / p mod q) + xp */
+ gcry_mpi_release(a);
+ gcry_mpi_release(u);
+}
+
+static void initialize_libgcrypt(void) {
+ const char *p;
+ if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
+ return;
+
+ p = gcry_check_version("1.4.5");
+ assert(p);
+
+ /* Turn off "secmem". Clients which whish to make use of this
+ * feature should initialize the library manually */
+ gcry_control(GCRYCTL_DISABLE_SECMEM);
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+}
+
+/******************************************************************************/
+
+size_t FSPRG_mskinbytes(unsigned _secpar) {
+ VALIDATE_SECPAR(_secpar);
+ return 2 + 2 * (_secpar / 2) / 8; /* to store header,p,q */
+}
+
+size_t FSPRG_mpkinbytes(unsigned _secpar) {
+ VALIDATE_SECPAR(_secpar);
+ return 2 + _secpar / 8; /* to store header,n */
+}
+
+size_t FSPRG_stateinbytes(unsigned _secpar) {
+ VALIDATE_SECPAR(_secpar);
+ return 2 + 2 * _secpar / 8 + 8; /* to store header,n,x,epoch */
+}
+
+static void store_secpar(void *buf, uint16_t secpar) {
+ secpar = secpar / 16 - 1;
+ ((uint8_t*) buf)[0] = (secpar >> 8) & 0xff;
+ ((uint8_t*) buf)[1] = (secpar >> 0) & 0xff;
+}
+
+static uint16_t read_secpar(const void *buf) {
+ uint16_t secpar;
+ secpar =
+ (uint16_t)(((uint8_t*) buf)[0]) << 8 |
+ (uint16_t)(((uint8_t*) buf)[1]) << 0;
+ return 16 * (secpar + 1);
+}
+
+void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned _secpar) {
+ uint8_t iseed[FSPRG_RECOMMENDED_SEEDLEN];
+ gcry_mpi_t n, p, q;
+ uint16_t secpar;
+
+ VALIDATE_SECPAR(_secpar);
+ secpar = _secpar;
+
+ initialize_libgcrypt();
+
+ if (!seed) {
+ gcry_randomize(iseed, FSPRG_RECOMMENDED_SEEDLEN, GCRY_STRONG_RANDOM);
+ seed = iseed;
+ seedlen = FSPRG_RECOMMENDED_SEEDLEN;
+ }
+
+ p = genprime3mod4(secpar / 2, seed, seedlen, RND_GEN_P);
+ q = genprime3mod4(secpar / 2, seed, seedlen, RND_GEN_Q);
+
+ if (msk) {
+ store_secpar(msk + 0, secpar);
+ mpi_export(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8, p);
+ mpi_export(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8, q);
+ }
+
+ if (mpk) {
+ n = gcry_mpi_new(0);
+ gcry_mpi_mul(n, p, q);
+ assert(gcry_mpi_get_nbits(n) == secpar);
+
+ store_secpar(mpk + 0, secpar);
+ mpi_export(mpk + 2, secpar / 8, n);
+
+ gcry_mpi_release(n);
+ }
+
+ gcry_mpi_release(p);
+ gcry_mpi_release(q);
+}
+
+void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen) {
+ gcry_mpi_t n, x;
+ uint16_t secpar;
+
+ initialize_libgcrypt();
+
+ secpar = read_secpar(mpk + 0);
+ n = mpi_import(mpk + 2, secpar / 8);
+ x = gensquare(n, seed, seedlen, RND_GEN_X, secpar);
+
+ memcpy(state, mpk, 2 + secpar / 8);
+ mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x);
+ memset(state + 2 + 2 * secpar / 8, 0, 8);
+
+ gcry_mpi_release(n);
+ gcry_mpi_release(x);
+}
+
+void FSPRG_Evolve(void *state) {
+ gcry_mpi_t n, x;
+ uint16_t secpar;
+ uint64_t epoch;
+
+ initialize_libgcrypt();
+
+ secpar = read_secpar(state + 0);
+ n = mpi_import(state + 2 + 0 * secpar / 8, secpar / 8);
+ x = mpi_import(state + 2 + 1 * secpar / 8, secpar / 8);
+ epoch = uint64_import(state + 2 + 2 * secpar / 8, 8);
+
+ gcry_mpi_mulm(x, x, x, n);
+ epoch++;
+
+ mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x);
+ uint64_export(state + 2 + 2 * secpar / 8, 8, epoch);
+
+ gcry_mpi_release(n);
+ gcry_mpi_release(x);
+}
+
+uint64_t FSPRG_GetEpoch(const void *state) {
+ uint16_t secpar;
+ secpar = read_secpar(state + 0);
+ return uint64_import(state + 2 + 2 * secpar / 8, 8);
+}
+
+void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen) {
+ gcry_mpi_t p, q, n, x, xp, xq, kp, kq, xm;
+ uint16_t secpar;
+
+ initialize_libgcrypt();
+
+ secpar = read_secpar(msk + 0);
+ p = mpi_import(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8);
+ q = mpi_import(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8);
+
+ n = gcry_mpi_new(0);
+ gcry_mpi_mul(n, p, q);
+
+ x = gensquare(n, seed, seedlen, RND_GEN_X, secpar);
+ CRT_decompose(&xp, &xq, x, p, q); /* split (mod n) into (mod p) and (mod q) using CRT */
+
+ kp = twopowmodphi(epoch, p); /* compute 2^epoch (mod phi(p)) */
+ kq = twopowmodphi(epoch, q); /* compute 2^epoch (mod phi(q)) */
+
+ gcry_mpi_powm(xp, xp, kp, p); /* compute x^(2^epoch) (mod p) */
+ gcry_mpi_powm(xq, xq, kq, q); /* compute x^(2^epoch) (mod q) */
+
+ CRT_compose(&xm, xp, xq, p, q); /* combine (mod p) and (mod q) to (mod n) using CRT */
+
+ store_secpar(state + 0, secpar);
+ mpi_export(state + 2 + 0 * secpar / 8, secpar / 8, n);
+ mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, xm);
+ uint64_export(state + 2 + 2 * secpar / 8, 8, epoch);
+
+ gcry_mpi_release(p);
+ gcry_mpi_release(q);
+ gcry_mpi_release(n);
+ gcry_mpi_release(x);
+ gcry_mpi_release(xp);
+ gcry_mpi_release(xq);
+ gcry_mpi_release(kp);
+ gcry_mpi_release(kq);
+ gcry_mpi_release(xm);
+}
+
+void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx) {
+ uint16_t secpar;
+
+ initialize_libgcrypt();
+
+ secpar = read_secpar(state + 0);
+ det_randomize(key, keylen, state + 2, 2 * secpar / 8 + 8, idx);
+}
diff --git a/src/journal/fsprg.h b/src/journal/fsprg.h
new file mode 100644
index 0000000000..306ef18d73
--- /dev/null
+++ b/src/journal/fsprg.h
@@ -0,0 +1,64 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef __fsprgh__
+#define __fsprgh__
+
+/*
+ * fsprg v0.1 - (seekable) forward-secure pseudorandom generator
+ * Copyright (C) 2012 B. Poettering
+ * Contact: fsprg@point-at-infinity.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define FSPRG_RECOMMENDED_SECPAR 1536
+#define FSPRG_RECOMMENDED_SEEDLEN (96/8)
+
+size_t FSPRG_mskinbytes(unsigned secpar);
+size_t FSPRG_mpkinbytes(unsigned secpar);
+size_t FSPRG_stateinbytes(unsigned secpar);
+
+/* Setup msk and mpk. Providing seed != NULL makes this algorithm deterministic. */
+void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned secpar);
+
+/* Initialize state deterministically in dependence on seed. */
+/* Note: in case one wants to run only one GenState0 per GenMK it is safe to use
+ the same seed for both GenMK and GenState0.
+*/
+void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen);
+
+void FSPRG_Evolve(void *state);
+
+uint64_t FSPRG_GetEpoch(const void *state);
+
+/* Seek to any arbitrary state (by providing msk together with seed from GenState0). */
+void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen);
+
+void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/journal/journal-authenticate.c b/src/journal/journal-authenticate.c
new file mode 100644
index 0000000000..64bf96874e
--- /dev/null
+++ b/src/journal/journal-authenticate.c
@@ -0,0 +1,563 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include "journal-def.h"
+#include "journal-file.h"
+#include "journal-authenticate.h"
+#include "fsprg.h"
+
+static uint64_t journal_file_tag_seqnum(JournalFile *f) {
+ uint64_t r;
+
+ assert(f);
+
+ r = le64toh(f->header->n_tags) + 1;
+ f->header->n_tags = htole64(r);
+
+ return r;
+}
+
+int journal_file_append_tag(JournalFile *f) {
+ Object *o;
+ uint64_t p;
+ int r;
+
+ assert(f);
+
+ if (!f->seal)
+ return 0;
+
+ if (!f->hmac_running)
+ return 0;
+
+ assert(f->hmac);
+
+ r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p);
+ if (r < 0)
+ return r;
+
+ o->tag.seqnum = htole64(journal_file_tag_seqnum(f));
+ o->tag.epoch = htole64(FSPRG_GetEpoch(f->fsprg_state));
+
+ log_debug("Writing tag %llu for epoch %llu\n",
+ (unsigned long long) le64toh(o->tag.seqnum),
+ (unsigned long long) FSPRG_GetEpoch(f->fsprg_state));
+
+ /* Add the tag object itself, so that we can protect its
+ * header. This will exclude the actual hash value in it */
+ r = journal_file_hmac_put_object(f, OBJECT_TAG, o, p);
+ if (r < 0)
+ return r;
+
+ /* Get the HMAC tag and store it in the object */
+ memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH);
+ f->hmac_running = false;
+
+ return 0;
+}
+
+int journal_file_hmac_start(JournalFile *f) {
+ uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */
+ assert(f);
+
+ if (!f->seal)
+ return 0;
+
+ if (f->hmac_running)
+ return 0;
+
+ /* Prepare HMAC for next cycle */
+ gcry_md_reset(f->hmac);
+ FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0);
+ gcry_md_setkey(f->hmac, key, sizeof(key));
+
+ f->hmac_running = true;
+
+ return 0;
+}
+
+static int journal_file_get_epoch(JournalFile *f, uint64_t realtime, uint64_t *epoch) {
+ uint64_t t;
+
+ assert(f);
+ assert(epoch);
+ assert(f->seal);
+
+ if (f->fss_start_usec == 0 ||
+ f->fss_interval_usec == 0)
+ return -ENOTSUP;
+
+ if (realtime < f->fss_start_usec)
+ return -ESTALE;
+
+ t = realtime - f->fss_start_usec;
+ t = t / f->fss_interval_usec;
+
+ *epoch = t;
+ return 0;
+}
+
+static int journal_file_fsprg_need_evolve(JournalFile *f, uint64_t realtime) {
+ uint64_t goal, epoch;
+ int r;
+ assert(f);
+
+ if (!f->seal)
+ return 0;
+
+ r = journal_file_get_epoch(f, realtime, &goal);
+ if (r < 0)
+ return r;
+
+ epoch = FSPRG_GetEpoch(f->fsprg_state);
+ if (epoch > goal)
+ return -ESTALE;
+
+ return epoch != goal;
+}
+
+int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) {
+ uint64_t goal, epoch;
+ int r;
+
+ assert(f);
+
+ if (!f->seal)
+ return 0;
+
+ r = journal_file_get_epoch(f, realtime, &goal);
+ if (r < 0)
+ return r;
+
+ epoch = FSPRG_GetEpoch(f->fsprg_state);
+ if (epoch < goal)
+ log_debug("Evolving FSPRG key from epoch %llu to %llu.", (unsigned long long) epoch, (unsigned long long) goal);
+
+ for (;;) {
+ if (epoch > goal)
+ return -ESTALE;
+ if (epoch == goal)
+ return 0;
+
+ FSPRG_Evolve(f->fsprg_state);
+ epoch = FSPRG_GetEpoch(f->fsprg_state);
+ }
+}
+
+int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) {
+ void *msk;
+ uint64_t epoch;
+
+ assert(f);
+
+ if (!f->seal)
+ return 0;
+
+ assert(f->fsprg_seed);
+
+ if (f->fsprg_state) {
+ /* Cheaper... */
+
+ epoch = FSPRG_GetEpoch(f->fsprg_state);
+ if (goal == epoch)
+ return 0;
+
+ if (goal == epoch+1) {
+ FSPRG_Evolve(f->fsprg_state);
+ return 0;
+ }
+ } else {
+ f->fsprg_state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
+ f->fsprg_state = malloc(f->fsprg_state_size);
+
+ if (!f->fsprg_state)
+ return -ENOMEM;
+ }
+
+ log_debug("Seeking FSPRG key to %llu.", (unsigned long long) goal);
+
+ msk = alloca(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR));
+ FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR);
+ FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size);
+ return 0;
+}
+
+int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) {
+ int r;
+
+ assert(f);
+
+ if (!f->seal)
+ return 0;
+
+ if (realtime <= 0)
+ realtime = now(CLOCK_REALTIME);
+
+ r = journal_file_fsprg_need_evolve(f, realtime);
+ if (r <= 0)
+ return 0;
+
+ r = journal_file_append_tag(f);
+ if (r < 0)
+ return r;
+
+ r = journal_file_fsprg_evolve(f, realtime);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int journal_file_hmac_put_object(JournalFile *f, int type, Object *o, uint64_t p) {
+ int r;
+
+ assert(f);
+
+ if (!f->seal)
+ return 0;
+
+ r = journal_file_hmac_start(f);
+ if (r < 0)
+ return r;
+
+ if (!o) {
+ r = journal_file_move_to_object(f, type, p, &o);
+ if (r < 0)
+ return r;
+ } else {
+ if (type >= 0 && o->object.type != type)
+ return -EBADMSG;
+ }
+
+ gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload));
+
+ switch (o->object.type) {
+
+ case OBJECT_DATA:
+ /* All but hash and payload are mutable */
+ gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash));
+ gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload));
+ break;
+
+ case OBJECT_FIELD:
+ /* Same here */
+ gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash));
+ gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(FieldObject, payload));
+ break;
+
+ case OBJECT_ENTRY:
+ /* All */
+ gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(EntryObject, seqnum));
+ break;
+
+ case OBJECT_FIELD_HASH_TABLE:
+ case OBJECT_DATA_HASH_TABLE:
+ case OBJECT_ENTRY_ARRAY:
+ /* Nothing: everything is mutable */
+ break;
+
+ case OBJECT_TAG:
+ /* All but the tag itself */
+ gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum));
+ gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int journal_file_hmac_put_header(JournalFile *f) {
+ int r;
+
+ assert(f);
+
+ if (!f->seal)
+ return 0;
+
+ r = journal_file_hmac_start(f);
+ if (r < 0)
+ return r;
+
+ /* All but state+reserved, boot_id, arena_size,
+ * tail_object_offset, n_objects, n_entries,
+ * tail_entry_seqnum, head_entry_seqnum, entry_array_offset,
+ * head_entry_realtime, tail_entry_realtime,
+ * tail_entry_monotonic, n_data, n_fields, n_tags,
+ * n_entry_arrays. */
+
+ gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature));
+ gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, boot_id) - offsetof(Header, file_id));
+ gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id));
+ gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset));
+
+ return 0;
+}
+
+int journal_file_fss_load(JournalFile *f) {
+ int r, fd = -1;
+ char *p = NULL;
+ struct stat st;
+ FSSHeader *m = NULL;
+ sd_id128_t machine;
+
+ assert(f);
+
+ if (!f->seal)
+ return 0;
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return r;
+
+ if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
+ SD_ID128_FORMAT_VAL(machine)) < 0)
+ return -ENOMEM;
+
+ fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
+ if (fd < 0) {
+ if (errno != ENOENT)
+ log_error("Failed to open %s: %m", p);
+
+ r = -errno;
+ goto finish;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (st.st_size < (off_t) sizeof(FSSHeader)) {
+ r = -ENODATA;
+ goto finish;
+ }
+
+ m = mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0);
+ if (m == MAP_FAILED) {
+ m = NULL;
+ r = -errno;
+ goto finish;
+ }
+
+ if (memcmp(m->signature, FSS_HEADER_SIGNATURE, 8) != 0) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ if (m->incompatible_flags != 0) {
+ r = -EPROTONOSUPPORT;
+ goto finish;
+ }
+
+ if (le64toh(m->header_size) < sizeof(FSSHeader)) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ if (le64toh(m->fsprg_state_size) != FSPRG_stateinbytes(le16toh(m->fsprg_secpar))) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ f->fss_file_size = le64toh(m->header_size) + le64toh(m->fsprg_state_size);
+ if ((uint64_t) st.st_size < f->fss_file_size) {
+ r = -ENODATA;
+ goto finish;
+ }
+
+ if (!sd_id128_equal(machine, m->machine_id)) {
+ r = -EHOSTDOWN;
+ goto finish;
+ }
+
+ if (le64toh(m->start_usec) <= 0 ||
+ le64toh(m->interval_usec) <= 0) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ f->fss_file = mmap(NULL, PAGE_ALIGN(f->fss_file_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ if (f->fss_file == MAP_FAILED) {
+ f->fss_file = NULL;
+ r = -errno;
+ goto finish;
+ }
+
+ f->fss_start_usec = le64toh(f->fss_file->start_usec);
+ f->fss_interval_usec = le64toh(f->fss_file->interval_usec);
+
+ f->fsprg_state = (uint8_t*) f->fss_file + le64toh(f->fss_file->header_size);
+ f->fsprg_state_size = le64toh(f->fss_file->fsprg_state_size);
+
+ r = 0;
+
+finish:
+ if (m)
+ munmap(m, PAGE_ALIGN(sizeof(FSSHeader)));
+
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ free(p);
+ return r;
+}
+
+static void initialize_libgcrypt(void) {
+ const char *p;
+
+ if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
+ return;
+
+ p = gcry_check_version("1.4.5");
+ assert(p);
+
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+}
+
+int journal_file_hmac_setup(JournalFile *f) {
+ gcry_error_t e;
+
+ if (!f->seal)
+ return 0;
+
+ initialize_libgcrypt();
+
+ e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+ if (e != 0)
+ return -ENOTSUP;
+
+ return 0;
+}
+
+int journal_file_append_first_tag(JournalFile *f) {
+ int r;
+ uint64_t p;
+
+ if (!f->seal)
+ return 0;
+
+ log_debug("Calculating first tag...");
+
+ r = journal_file_hmac_put_header(f);
+ if (r < 0)
+ return r;
+
+ p = le64toh(f->header->field_hash_table_offset);
+ if (p < offsetof(Object, hash_table.items))
+ return -EINVAL;
+ p -= offsetof(Object, hash_table.items);
+
+ r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, NULL, p);
+ if (r < 0)
+ return r;
+
+ p = le64toh(f->header->data_hash_table_offset);
+ if (p < offsetof(Object, hash_table.items))
+ return -EINVAL;
+ p -= offsetof(Object, hash_table.items);
+
+ r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, NULL, p);
+ if (r < 0)
+ return r;
+
+ r = journal_file_append_tag(f);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int journal_file_parse_verification_key(JournalFile *f, const char *key) {
+ uint8_t *seed;
+ size_t seed_size, c;
+ const char *k;
+ int r;
+ unsigned long long start, interval;
+
+ seed_size = FSPRG_RECOMMENDED_SEEDLEN;
+ seed = malloc(seed_size);
+ if (!seed)
+ return -ENOMEM;
+
+ k = key;
+ for (c = 0; c < seed_size; c++) {
+ int x, y;
+
+ while (*k == '-')
+ k++;
+
+ x = unhexchar(*k);
+ if (x < 0) {
+ free(seed);
+ return -EINVAL;
+ }
+ k++;
+ y = unhexchar(*k);
+ if (y < 0) {
+ free(seed);
+ return -EINVAL;
+ }
+ k++;
+
+ seed[c] = (uint8_t) (x * 16 + y);
+ }
+
+ if (*k != '/') {
+ free(seed);
+ return -EINVAL;
+ }
+ k++;
+
+ r = sscanf(k, "%llx-%llx", &start, &interval);
+ if (r != 2) {
+ free(seed);
+ return -EINVAL;
+ }
+
+ f->fsprg_seed = seed;
+ f->fsprg_seed_size = seed_size;
+
+ f->fss_start_usec = start * interval;
+ f->fss_interval_usec = interval;
+
+ return 0;
+}
+
+bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u) {
+ uint64_t epoch;
+
+ assert(f);
+ assert(u);
+
+ if (!f->seal)
+ return false;
+
+ epoch = FSPRG_GetEpoch(f->fsprg_state);
+
+ *u = (usec_t) (f->fss_start_usec + f->fss_interval_usec * epoch + f->fss_interval_usec);
+
+ return true;
+}
diff --git a/src/journal/journal-authenticate.h b/src/journal/journal-authenticate.h
new file mode 100644
index 0000000000..0aaf836721
--- /dev/null
+++ b/src/journal/journal-authenticate.h
@@ -0,0 +1,44 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+#include "journal-file.h"
+
+int journal_file_append_tag(JournalFile *f);
+int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime);
+int journal_file_append_first_tag(JournalFile *f);
+
+int journal_file_hmac_setup(JournalFile *f);
+int journal_file_hmac_start(JournalFile *f);
+int journal_file_hmac_put_header(JournalFile *f);
+int journal_file_hmac_put_object(JournalFile *f, int type, Object *o, uint64_t p);
+
+int journal_file_fss_load(JournalFile *f);
+int journal_file_parse_verification_key(JournalFile *f, const char *key);
+
+int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime);
+int journal_file_fsprg_seek(JournalFile *f, uint64_t epoch);
+
+bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u);
diff --git a/src/journal/journal-def.h b/src/journal/journal-def.h
new file mode 100644
index 0000000000..ac5611c310
--- /dev/null
+++ b/src/journal/journal-def.h
@@ -0,0 +1,216 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "sparse-endian.h"
+
+#include <systemd/sd-id128.h>
+
+#include "macro.h"
+
+/*
+ * If you change this file you probably should also change its documentation:
+ *
+ * http://www.freedesktop.org/wiki/Software/systemd/journal-files
+ *
+ */
+
+typedef struct Header Header;
+
+typedef struct ObjectHeader ObjectHeader;
+typedef union Object Object;
+
+typedef struct DataObject DataObject;
+typedef struct FieldObject FieldObject;
+typedef struct EntryObject EntryObject;
+typedef struct HashTableObject HashTableObject;
+typedef struct EntryArrayObject EntryArrayObject;
+typedef struct TagObject TagObject;
+
+typedef struct EntryItem EntryItem;
+typedef struct HashItem HashItem;
+
+typedef struct FSSHeader FSSHeader;
+
+/* Object types */
+enum {
+ OBJECT_UNUSED,
+ OBJECT_DATA,
+ OBJECT_FIELD,
+ OBJECT_ENTRY,
+ OBJECT_DATA_HASH_TABLE,
+ OBJECT_FIELD_HASH_TABLE,
+ OBJECT_ENTRY_ARRAY,
+ OBJECT_TAG,
+ _OBJECT_TYPE_MAX
+};
+
+/* Object flags */
+enum {
+ OBJECT_COMPRESSED = 1
+};
+
+_packed_ struct ObjectHeader {
+ uint8_t type;
+ uint8_t flags;
+ uint8_t reserved[6];
+ le64_t size;
+ uint8_t payload[];
+};
+
+_packed_ struct DataObject {
+ ObjectHeader object;
+ le64_t hash;
+ le64_t next_hash_offset;
+ le64_t next_field_offset;
+ le64_t entry_offset; /* the first array entry we store inline */
+ le64_t entry_array_offset;
+ le64_t n_entries;
+ uint8_t payload[];
+};
+
+_packed_ struct FieldObject {
+ ObjectHeader object;
+ le64_t hash;
+ le64_t next_hash_offset;
+ le64_t head_data_offset;
+ uint8_t payload[];
+};
+
+_packed_ struct EntryItem {
+ le64_t object_offset;
+ le64_t hash;
+};
+
+_packed_ struct EntryObject {
+ ObjectHeader object;
+ le64_t seqnum;
+ le64_t realtime;
+ le64_t monotonic;
+ sd_id128_t boot_id;
+ le64_t xor_hash;
+ EntryItem items[];
+};
+
+_packed_ struct HashItem {
+ le64_t head_hash_offset;
+ le64_t tail_hash_offset;
+};
+
+_packed_ struct HashTableObject {
+ ObjectHeader object;
+ HashItem items[];
+};
+
+_packed_ struct EntryArrayObject {
+ ObjectHeader object;
+ le64_t next_entry_array_offset;
+ le64_t items[];
+};
+
+#define TAG_LENGTH (256/8)
+
+_packed_ struct TagObject {
+ ObjectHeader object;
+ le64_t seqnum;
+ le64_t epoch;
+ uint8_t tag[TAG_LENGTH]; /* SHA-256 HMAC */
+};
+
+union Object {
+ ObjectHeader object;
+ DataObject data;
+ FieldObject field;
+ EntryObject entry;
+ HashTableObject hash_table;
+ EntryArrayObject entry_array;
+ TagObject tag;
+};
+
+enum {
+ STATE_OFFLINE = 0,
+ STATE_ONLINE = 1,
+ STATE_ARCHIVED = 2,
+ _STATE_MAX
+};
+
+/* Header flags */
+enum {
+ HEADER_INCOMPATIBLE_COMPRESSED = 1
+};
+
+enum {
+ HEADER_COMPATIBLE_SEALED = 1
+};
+
+#define HEADER_SIGNATURE ((char[]) { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' })
+
+_packed_ struct Header {
+ uint8_t signature[8]; /* "LPKSHHRH" */
+ le32_t compatible_flags;
+ le32_t incompatible_flags;
+ uint8_t state;
+ uint8_t reserved[7];
+ sd_id128_t file_id;
+ sd_id128_t machine_id;
+ sd_id128_t boot_id; /* last writer */
+ sd_id128_t seqnum_id;
+ le64_t header_size;
+ le64_t arena_size;
+ le64_t data_hash_table_offset;
+ le64_t data_hash_table_size;
+ le64_t field_hash_table_offset;
+ le64_t field_hash_table_size;
+ le64_t tail_object_offset;
+ le64_t n_objects;
+ le64_t n_entries;
+ le64_t tail_entry_seqnum;
+ le64_t head_entry_seqnum;
+ le64_t entry_array_offset;
+ le64_t head_entry_realtime;
+ le64_t tail_entry_realtime;
+ le64_t tail_entry_monotonic;
+ /* Added in 187 */
+ le64_t n_data;
+ le64_t n_fields;
+ /* Added in 189 */
+ le64_t n_tags;
+ le64_t n_entry_arrays;
+
+ /* Size: 224 */
+};
+
+#define FSS_HEADER_SIGNATURE ((char[]) { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' })
+
+_packed_ struct FSSHeader {
+ uint8_t signature[8]; /* "KSHHRHLP" */
+ le32_t compatible_flags;
+ le32_t incompatible_flags;
+ sd_id128_t machine_id;
+ sd_id128_t boot_id; /* last writer */
+ le64_t header_size;
+ le64_t start_usec;
+ le64_t interval_usec;
+ le16_t fsprg_secpar;
+ le16_t reserved[3];
+ le64_t fsprg_state_size;
+};
diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c
new file mode 100644
index 0000000000..3df099dbd2
--- /dev/null
+++ b/src/journal/journal-file.c
@@ -0,0 +1,2872 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/mman.h>
+#include <errno.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <sys/statvfs.h>
+#include <fcntl.h>
+#include <stddef.h>
+
+#ifdef HAVE_XATTR
+#include <attr/xattr.h>
+#endif
+
+#include "journal-def.h"
+#include "journal-file.h"
+#include "journal-authenticate.h"
+#include "lookup3.h"
+#include "compress.h"
+#include "fsprg.h"
+
+#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem))
+#define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem))
+
+#define COMPRESSION_SIZE_THRESHOLD (512ULL)
+
+/* This is the minimum journal file size */
+#define JOURNAL_FILE_SIZE_MIN (64ULL*1024ULL) /* 64 KiB */
+
+/* These are the lower and upper bounds if we deduce the max_use value
+ * from the file system size */
+#define DEFAULT_MAX_USE_LOWER (1ULL*1024ULL*1024ULL) /* 1 MiB */
+#define DEFAULT_MAX_USE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
+
+/* This is the upper bound if we deduce max_size from max_use */
+#define DEFAULT_MAX_SIZE_UPPER (128ULL*1024ULL*1024ULL) /* 128 MiB */
+
+/* This is the upper bound if we deduce the keep_free value from the
+ * file system size */
+#define DEFAULT_KEEP_FREE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
+
+/* This is the keep_free value when we can't determine the system
+ * size */
+#define DEFAULT_KEEP_FREE (1024ULL*1024ULL) /* 1 MB */
+
+/* n_data was the first entry we added after the initial file format design */
+#define HEADER_SIZE_MIN ALIGN64(offsetof(Header, n_data))
+
+/* How many entries to keep in the entry array chain cache at max */
+#define CHAIN_CACHE_MAX 20
+
+void journal_file_close(JournalFile *f) {
+ assert(f);
+
+#ifdef HAVE_GCRYPT
+ /* Write the final tag */
+ if (f->seal && f->writable)
+ journal_file_append_tag(f);
+#endif
+
+ /* Sync everything to disk, before we mark the file offline */
+ if (f->mmap && f->fd >= 0)
+ mmap_cache_close_fd(f->mmap, f->fd);
+
+ if (f->writable && f->fd >= 0)
+ fdatasync(f->fd);
+
+ if (f->header) {
+ /* Mark the file offline. Don't override the archived state if it already is set */
+ if (f->writable && f->header->state == STATE_ONLINE)
+ f->header->state = STATE_OFFLINE;
+
+ munmap(f->header, PAGE_ALIGN(sizeof(Header)));
+ }
+
+ if (f->fd >= 0)
+ close_nointr_nofail(f->fd);
+
+ free(f->path);
+
+ if (f->mmap)
+ mmap_cache_unref(f->mmap);
+
+ hashmap_free_free(f->chain_cache);
+
+#ifdef HAVE_XZ
+ free(f->compress_buffer);
+#endif
+
+#ifdef HAVE_GCRYPT
+ if (f->fss_file)
+ munmap(f->fss_file, PAGE_ALIGN(f->fss_file_size));
+ else if (f->fsprg_state)
+ free(f->fsprg_state);
+
+ free(f->fsprg_seed);
+
+ if (f->hmac)
+ gcry_md_close(f->hmac);
+#endif
+
+ free(f);
+}
+
+static int journal_file_init_header(JournalFile *f, JournalFile *template) {
+ Header h;
+ ssize_t k;
+ int r;
+
+ assert(f);
+
+ zero(h);
+ memcpy(h.signature, HEADER_SIGNATURE, 8);
+ h.header_size = htole64(ALIGN64(sizeof(h)));
+
+ h.incompatible_flags =
+ htole32(f->compress ? HEADER_INCOMPATIBLE_COMPRESSED : 0);
+
+ h.compatible_flags =
+ htole32(f->seal ? HEADER_COMPATIBLE_SEALED : 0);
+
+ r = sd_id128_randomize(&h.file_id);
+ if (r < 0)
+ return r;
+
+ if (template) {
+ h.seqnum_id = template->header->seqnum_id;
+ h.tail_entry_seqnum = template->header->tail_entry_seqnum;
+ } else
+ h.seqnum_id = h.file_id;
+
+ k = pwrite(f->fd, &h, sizeof(h), 0);
+ if (k < 0)
+ return -errno;
+
+ if (k != sizeof(h))
+ return -EIO;
+
+ return 0;
+}
+
+static int journal_file_refresh_header(JournalFile *f) {
+ int r;
+ sd_id128_t boot_id;
+
+ assert(f);
+
+ r = sd_id128_get_machine(&f->header->machine_id);
+ if (r < 0)
+ return r;
+
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0)
+ return r;
+
+ if (sd_id128_equal(boot_id, f->header->boot_id))
+ f->tail_entry_monotonic_valid = true;
+
+ f->header->boot_id = boot_id;
+
+ f->header->state = STATE_ONLINE;
+
+ /* Sync the online state to disk */
+ msync(f->header, PAGE_ALIGN(sizeof(Header)), MS_SYNC);
+ fdatasync(f->fd);
+
+ return 0;
+}
+
+static int journal_file_verify_header(JournalFile *f) {
+ assert(f);
+
+ if (memcmp(f->header->signature, HEADER_SIGNATURE, 8))
+ return -EBADMSG;
+
+ /* In both read and write mode we refuse to open files with
+ * incompatible flags we don't know */
+#ifdef HAVE_XZ
+ if ((le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) != 0)
+ return -EPROTONOSUPPORT;
+#else
+ if (f->header->incompatible_flags != 0)
+ return -EPROTONOSUPPORT;
+#endif
+
+ /* When open for writing we refuse to open files with
+ * compatible flags, too */
+ if (f->writable) {
+#ifdef HAVE_GCRYPT
+ if ((le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SEALED) != 0)
+ return -EPROTONOSUPPORT;
+#else
+ if (f->header->compatible_flags != 0)
+ return -EPROTONOSUPPORT;
+#endif
+ }
+
+ if (f->header->state >= _STATE_MAX)
+ return -EBADMSG;
+
+ /* The first addition was n_data, so check that we are at least this large */
+ if (le64toh(f->header->header_size) < HEADER_SIZE_MIN)
+ return -EBADMSG;
+
+ if (JOURNAL_HEADER_SEALED(f->header) && !JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
+ return -EBADMSG;
+
+ if ((le64toh(f->header->header_size) + le64toh(f->header->arena_size)) > (uint64_t) f->last_stat.st_size)
+ return -ENODATA;
+
+ if (le64toh(f->header->tail_object_offset) > (le64toh(f->header->header_size) + le64toh(f->header->arena_size)))
+ return -ENODATA;
+
+ if (!VALID64(le64toh(f->header->data_hash_table_offset)) ||
+ !VALID64(le64toh(f->header->field_hash_table_offset)) ||
+ !VALID64(le64toh(f->header->tail_object_offset)) ||
+ !VALID64(le64toh(f->header->entry_array_offset)))
+ return -ENODATA;
+
+ if (le64toh(f->header->data_hash_table_offset) < le64toh(f->header->header_size) ||
+ le64toh(f->header->field_hash_table_offset) < le64toh(f->header->header_size) ||
+ le64toh(f->header->tail_object_offset) < le64toh(f->header->header_size) ||
+ le64toh(f->header->entry_array_offset) < le64toh(f->header->header_size))
+ return -ENODATA;
+
+ if (f->writable) {
+ uint8_t state;
+ sd_id128_t machine_id;
+ int r;
+
+ r = sd_id128_get_machine(&machine_id);
+ if (r < 0)
+ return r;
+
+ if (!sd_id128_equal(machine_id, f->header->machine_id))
+ return -EHOSTDOWN;
+
+ state = f->header->state;
+
+ if (state == STATE_ONLINE) {
+ log_debug("Journal file %s is already online. Assuming unclean closing.", f->path);
+ return -EBUSY;
+ } else if (state == STATE_ARCHIVED)
+ return -ESHUTDOWN;
+ else if (state != STATE_OFFLINE) {
+ log_debug("Journal file %s has unknown state %u.", f->path, state);
+ return -EBUSY;
+ }
+ }
+
+ f->compress = JOURNAL_HEADER_COMPRESSED(f->header);
+
+ f->seal = JOURNAL_HEADER_SEALED(f->header);
+
+ return 0;
+}
+
+static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
+ uint64_t old_size, new_size;
+ int r;
+
+ assert(f);
+
+ /* We assume that this file is not sparse, and we know that
+ * for sure, since we always call posix_fallocate()
+ * ourselves */
+
+ old_size =
+ le64toh(f->header->header_size) +
+ le64toh(f->header->arena_size);
+
+ new_size = PAGE_ALIGN(offset + size);
+ if (new_size < le64toh(f->header->header_size))
+ new_size = le64toh(f->header->header_size);
+
+ if (new_size <= old_size)
+ return 0;
+
+ if (f->metrics.max_size > 0 &&
+ new_size > f->metrics.max_size)
+ return -E2BIG;
+
+ if (new_size > f->metrics.min_size &&
+ f->metrics.keep_free > 0) {
+ struct statvfs svfs;
+
+ if (fstatvfs(f->fd, &svfs) >= 0) {
+ uint64_t available;
+
+ available = svfs.f_bfree * svfs.f_bsize;
+
+ if (available >= f->metrics.keep_free)
+ available -= f->metrics.keep_free;
+ else
+ available = 0;
+
+ if (new_size - old_size > available)
+ return -E2BIG;
+ }
+ }
+
+ /* Note that the glibc fallocate() fallback is very
+ inefficient, hence we try to minimize the allocation area
+ as we can. */
+ r = posix_fallocate(f->fd, old_size, new_size - old_size);
+ if (r != 0)
+ return -r;
+
+ if (fstat(f->fd, &f->last_stat) < 0)
+ return -errno;
+
+ f->header->arena_size = htole64(new_size - le64toh(f->header->header_size));
+
+ return 0;
+}
+
+static int journal_file_move_to(JournalFile *f, int context, bool keep_always, uint64_t offset, uint64_t size, void **ret) {
+ assert(f);
+ assert(ret);
+
+ if (size <= 0)
+ return -EINVAL;
+
+ /* Avoid SIGBUS on invalid accesses */
+ if (offset + size > (uint64_t) f->last_stat.st_size) {
+ /* Hmm, out of range? Let's refresh the fstat() data
+ * first, before we trust that check. */
+
+ if (fstat(f->fd, &f->last_stat) < 0 ||
+ offset + size > (uint64_t) f->last_stat.st_size)
+ return -EADDRNOTAVAIL;
+ }
+
+ return mmap_cache_get(f->mmap, f->fd, f->prot, context, keep_always, offset, size, &f->last_stat, ret);
+}
+
+static uint64_t minimum_header_size(Object *o) {
+
+ static uint64_t table[] = {
+ [OBJECT_DATA] = sizeof(DataObject),
+ [OBJECT_FIELD] = sizeof(FieldObject),
+ [OBJECT_ENTRY] = sizeof(EntryObject),
+ [OBJECT_DATA_HASH_TABLE] = sizeof(HashTableObject),
+ [OBJECT_FIELD_HASH_TABLE] = sizeof(HashTableObject),
+ [OBJECT_ENTRY_ARRAY] = sizeof(EntryArrayObject),
+ [OBJECT_TAG] = sizeof(TagObject),
+ };
+
+ if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0)
+ return sizeof(ObjectHeader);
+
+ return table[o->object.type];
+}
+
+int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret) {
+ int r;
+ void *t;
+ Object *o;
+ uint64_t s;
+ unsigned context;
+
+ assert(f);
+ assert(ret);
+
+ /* Objects may only be located at multiple of 64 bit */
+ if (!VALID64(offset))
+ return -EFAULT;
+
+ /* One context for each type, plus one catch-all for the rest */
+ context = type > 0 && type < _OBJECT_TYPE_MAX ? type : 0;
+
+ r = journal_file_move_to(f, context, false, offset, sizeof(ObjectHeader), &t);
+ if (r < 0)
+ return r;
+
+ o = (Object*) t;
+ s = le64toh(o->object.size);
+
+ if (s < sizeof(ObjectHeader))
+ return -EBADMSG;
+
+ if (o->object.type <= OBJECT_UNUSED)
+ return -EBADMSG;
+
+ if (s < minimum_header_size(o))
+ return -EBADMSG;
+
+ if (type > 0 && o->object.type != type)
+ return -EBADMSG;
+
+ if (s > sizeof(ObjectHeader)) {
+ r = journal_file_move_to(f, o->object.type, false, offset, s, &t);
+ if (r < 0)
+ return r;
+
+ o = (Object*) t;
+ }
+
+ *ret = o;
+ return 0;
+}
+
+static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) {
+ uint64_t r;
+
+ assert(f);
+
+ r = le64toh(f->header->tail_entry_seqnum) + 1;
+
+ if (seqnum) {
+ /* If an external seqnum counter was passed, we update
+ * both the local and the external one, and set it to
+ * the maximum of both */
+
+ if (*seqnum + 1 > r)
+ r = *seqnum + 1;
+
+ *seqnum = r;
+ }
+
+ f->header->tail_entry_seqnum = htole64(r);
+
+ if (f->header->head_entry_seqnum == 0)
+ f->header->head_entry_seqnum = htole64(r);
+
+ return r;
+}
+
+int journal_file_append_object(JournalFile *f, int type, uint64_t size, Object **ret, uint64_t *offset) {
+ int r;
+ uint64_t p;
+ Object *tail, *o;
+ void *t;
+
+ assert(f);
+ assert(type > 0 && type < _OBJECT_TYPE_MAX);
+ assert(size >= sizeof(ObjectHeader));
+ assert(offset);
+ assert(ret);
+
+ p = le64toh(f->header->tail_object_offset);
+ if (p == 0)
+ p = le64toh(f->header->header_size);
+ else {
+ r = journal_file_move_to_object(f, -1, p, &tail);
+ if (r < 0)
+ return r;
+
+ p += ALIGN64(le64toh(tail->object.size));
+ }
+
+ r = journal_file_allocate(f, p, size);
+ if (r < 0)
+ return r;
+
+ r = journal_file_move_to(f, type, false, p, size, &t);
+ if (r < 0)
+ return r;
+
+ o = (Object*) t;
+
+ zero(o->object);
+ o->object.type = type;
+ o->object.size = htole64(size);
+
+ f->header->tail_object_offset = htole64(p);
+ f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1);
+
+ *ret = o;
+ *offset = p;
+
+ return 0;
+}
+
+static int journal_file_setup_data_hash_table(JournalFile *f) {
+ uint64_t s, p;
+ Object *o;
+ int r;
+
+ assert(f);
+
+ /* We estimate that we need 1 hash table entry per 768 of
+ journal file and we want to make sure we never get beyond
+ 75% fill level. Calculate the hash table size for the
+ maximum file size based on these metrics. */
+
+ s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem);
+ if (s < DEFAULT_DATA_HASH_TABLE_SIZE)
+ s = DEFAULT_DATA_HASH_TABLE_SIZE;
+
+ log_debug("Reserving %llu entries in hash table.", (unsigned long long) (s / sizeof(HashItem)));
+
+ r = journal_file_append_object(f,
+ OBJECT_DATA_HASH_TABLE,
+ offsetof(Object, hash_table.items) + s,
+ &o, &p);
+ if (r < 0)
+ return r;
+
+ memset(o->hash_table.items, 0, s);
+
+ f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
+ f->header->data_hash_table_size = htole64(s);
+
+ return 0;
+}
+
+static int journal_file_setup_field_hash_table(JournalFile *f) {
+ uint64_t s, p;
+ Object *o;
+ int r;
+
+ assert(f);
+
+ /* We use a fixed size hash table for the fields as this
+ * number should grow very slowly only */
+
+ s = DEFAULT_FIELD_HASH_TABLE_SIZE;
+ r = journal_file_append_object(f,
+ OBJECT_FIELD_HASH_TABLE,
+ offsetof(Object, hash_table.items) + s,
+ &o, &p);
+ if (r < 0)
+ return r;
+
+ memset(o->hash_table.items, 0, s);
+
+ f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
+ f->header->field_hash_table_size = htole64(s);
+
+ return 0;
+}
+
+static int journal_file_map_data_hash_table(JournalFile *f) {
+ uint64_t s, p;
+ void *t;
+ int r;
+
+ assert(f);
+
+ p = le64toh(f->header->data_hash_table_offset);
+ s = le64toh(f->header->data_hash_table_size);
+
+ r = journal_file_move_to(f,
+ OBJECT_DATA_HASH_TABLE,
+ true,
+ p, s,
+ &t);
+ if (r < 0)
+ return r;
+
+ f->data_hash_table = t;
+ return 0;
+}
+
+static int journal_file_map_field_hash_table(JournalFile *f) {
+ uint64_t s, p;
+ void *t;
+ int r;
+
+ assert(f);
+
+ p = le64toh(f->header->field_hash_table_offset);
+ s = le64toh(f->header->field_hash_table_size);
+
+ r = journal_file_move_to(f,
+ OBJECT_FIELD_HASH_TABLE,
+ true,
+ p, s,
+ &t);
+ if (r < 0)
+ return r;
+
+ f->field_hash_table = t;
+ return 0;
+}
+
+static int journal_file_link_field(
+ JournalFile *f,
+ Object *o,
+ uint64_t offset,
+ uint64_t hash) {
+
+ uint64_t p, h;
+ int r;
+
+ assert(f);
+ assert(o);
+ assert(offset > 0);
+
+ if (o->object.type != OBJECT_FIELD)
+ return -EINVAL;
+
+ /* This might alter the window we are looking at */
+
+ o->field.next_hash_offset = o->field.head_data_offset = 0;
+
+ h = hash % (le64toh(f->header->field_hash_table_size) / sizeof(HashItem));
+ p = le64toh(f->field_hash_table[h].tail_hash_offset);
+ if (p == 0)
+ f->field_hash_table[h].head_hash_offset = htole64(offset);
+ else {
+ r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o);
+ if (r < 0)
+ return r;
+
+ o->field.next_hash_offset = htole64(offset);
+ }
+
+ f->field_hash_table[h].tail_hash_offset = htole64(offset);
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
+ f->header->n_fields = htole64(le64toh(f->header->n_fields) + 1);
+
+ return 0;
+}
+
+static int journal_file_link_data(
+ JournalFile *f,
+ Object *o,
+ uint64_t offset,
+ uint64_t hash) {
+
+ uint64_t p, h;
+ int r;
+
+ assert(f);
+ assert(o);
+ assert(offset > 0);
+
+ if (o->object.type != OBJECT_DATA)
+ return -EINVAL;
+
+ /* This might alter the window we are looking at */
+
+ o->data.next_hash_offset = o->data.next_field_offset = 0;
+ o->data.entry_offset = o->data.entry_array_offset = 0;
+ o->data.n_entries = 0;
+
+ h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
+ p = le64toh(f->data_hash_table[h].tail_hash_offset);
+ if (p == 0)
+ /* Only entry in the hash table is easy */
+ f->data_hash_table[h].head_hash_offset = htole64(offset);
+ else {
+ /* Move back to the previous data object, to patch in
+ * pointer */
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+ if (r < 0)
+ return r;
+
+ o->data.next_hash_offset = htole64(offset);
+ }
+
+ f->data_hash_table[h].tail_hash_offset = htole64(offset);
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
+ f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
+
+ return 0;
+}
+
+int journal_file_find_field_object_with_hash(
+ JournalFile *f,
+ const void *field, uint64_t size, uint64_t hash,
+ Object **ret, uint64_t *offset) {
+
+ uint64_t p, osize, h;
+ int r;
+
+ assert(f);
+ assert(field && size > 0);
+
+ osize = offsetof(Object, field.payload) + size;
+
+ if (f->header->field_hash_table_size == 0)
+ return -EBADMSG;
+
+ h = hash % (le64toh(f->header->field_hash_table_size) / sizeof(HashItem));
+ p = le64toh(f->field_hash_table[h].head_hash_offset);
+
+ while (p > 0) {
+ Object *o;
+
+ r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o);
+ if (r < 0)
+ return r;
+
+ if (le64toh(o->field.hash) == hash &&
+ le64toh(o->object.size) == osize &&
+ memcmp(o->field.payload, field, size) == 0) {
+
+ if (ret)
+ *ret = o;
+ if (offset)
+ *offset = p;
+
+ return 1;
+ }
+
+ p = le64toh(o->field.next_hash_offset);
+ }
+
+ return 0;
+}
+
+int journal_file_find_field_object(
+ JournalFile *f,
+ const void *field, uint64_t size,
+ Object **ret, uint64_t *offset) {
+
+ uint64_t hash;
+
+ assert(f);
+ assert(field && size > 0);
+
+ hash = hash64(field, size);
+
+ return journal_file_find_field_object_with_hash(f,
+ field, size, hash,
+ ret, offset);
+}
+
+int journal_file_find_data_object_with_hash(
+ JournalFile *f,
+ const void *data, uint64_t size, uint64_t hash,
+ Object **ret, uint64_t *offset) {
+
+ uint64_t p, osize, h;
+ int r;
+
+ assert(f);
+ assert(data || size == 0);
+
+ osize = offsetof(Object, data.payload) + size;
+
+ if (f->header->data_hash_table_size == 0)
+ return -EBADMSG;
+
+ h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
+ p = le64toh(f->data_hash_table[h].head_hash_offset);
+
+ while (p > 0) {
+ Object *o;
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+ if (r < 0)
+ return r;
+
+ if (le64toh(o->data.hash) != hash)
+ goto next;
+
+ if (o->object.flags & OBJECT_COMPRESSED) {
+#ifdef HAVE_XZ
+ uint64_t l, rsize;
+
+ l = le64toh(o->object.size);
+ if (l <= offsetof(Object, data.payload))
+ return -EBADMSG;
+
+ l -= offsetof(Object, data.payload);
+
+ if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
+ return -EBADMSG;
+
+ if (rsize == size &&
+ memcmp(f->compress_buffer, data, size) == 0) {
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = p;
+
+ return 1;
+ }
+#else
+ return -EPROTONOSUPPORT;
+#endif
+
+ } else if (le64toh(o->object.size) == osize &&
+ memcmp(o->data.payload, data, size) == 0) {
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = p;
+
+ return 1;
+ }
+
+ next:
+ p = le64toh(o->data.next_hash_offset);
+ }
+
+ return 0;
+}
+
+int journal_file_find_data_object(
+ JournalFile *f,
+ const void *data, uint64_t size,
+ Object **ret, uint64_t *offset) {
+
+ uint64_t hash;
+
+ assert(f);
+ assert(data || size == 0);
+
+ hash = hash64(data, size);
+
+ return journal_file_find_data_object_with_hash(f,
+ data, size, hash,
+ ret, offset);
+}
+
+static int journal_file_append_field(
+ JournalFile *f,
+ const void *field, uint64_t size,
+ Object **ret, uint64_t *offset) {
+
+ uint64_t hash, p;
+ uint64_t osize;
+ Object *o;
+ int r;
+
+ assert(f);
+ assert(field && size > 0);
+
+ hash = hash64(field, size);
+
+ r = journal_file_find_field_object_with_hash(f, field, size, hash, &o, &p);
+ if (r < 0)
+ return r;
+ else if (r > 0) {
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = p;
+
+ return 0;
+ }
+
+ osize = offsetof(Object, field.payload) + size;
+ r = journal_file_append_object(f, OBJECT_FIELD, osize, &o, &p);
+
+ o->field.hash = htole64(hash);
+ memcpy(o->field.payload, field, size);
+
+ r = journal_file_link_field(f, o, p, hash);
+ if (r < 0)
+ return r;
+
+ /* The linking might have altered the window, so let's
+ * refresh our pointer */
+ r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o);
+ if (r < 0)
+ return r;
+
+#ifdef HAVE_GCRYPT
+ r = journal_file_hmac_put_object(f, OBJECT_FIELD, o, p);
+ if (r < 0)
+ return r;
+#endif
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = p;
+
+ return 0;
+}
+
+static int journal_file_append_data(
+ JournalFile *f,
+ const void *data, uint64_t size,
+ Object **ret, uint64_t *offset) {
+
+ uint64_t hash, p;
+ uint64_t osize;
+ Object *o;
+ int r;
+ bool compressed = false;
+ const void *eq;
+
+ assert(f);
+ assert(data || size == 0);
+
+ hash = hash64(data, size);
+
+ r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
+ if (r < 0)
+ return r;
+ else if (r > 0) {
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = p;
+
+ return 0;
+ }
+
+ osize = offsetof(Object, data.payload) + size;
+ r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
+ if (r < 0)
+ return r;
+
+ o->data.hash = htole64(hash);
+
+#ifdef HAVE_XZ
+ if (f->compress &&
+ size >= COMPRESSION_SIZE_THRESHOLD) {
+ uint64_t rsize;
+
+ compressed = compress_blob(data, size, o->data.payload, &rsize);
+
+ if (compressed) {
+ o->object.size = htole64(offsetof(Object, data.payload) + rsize);
+ o->object.flags |= OBJECT_COMPRESSED;
+
+ log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize);
+ }
+ }
+#endif
+
+ if (!compressed && size > 0)
+ memcpy(o->data.payload, data, size);
+
+ r = journal_file_link_data(f, o, p, hash);
+ if (r < 0)
+ return r;
+
+ /* The linking might have altered the window, so let's
+ * refresh our pointer */
+ r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+ if (r < 0)
+ return r;
+
+ eq = memchr(data, '=', size);
+ if (eq && eq > data) {
+ uint64_t fp;
+ Object *fo;
+
+ /* Create field object ... */
+ r = journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, &fo, &fp);
+ if (r < 0)
+ return r;
+
+ /* ... and link it in. */
+ o->data.next_field_offset = fo->field.head_data_offset;
+ fo->field.head_data_offset = le64toh(p);
+ }
+
+#ifdef HAVE_GCRYPT
+ r = journal_file_hmac_put_object(f, OBJECT_DATA, o, p);
+ if (r < 0)
+ return r;
+#endif
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = p;
+
+ return 0;
+}
+
+uint64_t journal_file_entry_n_items(Object *o) {
+ assert(o);
+
+ if (o->object.type != OBJECT_ENTRY)
+ return 0;
+
+ return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
+}
+
+uint64_t journal_file_entry_array_n_items(Object *o) {
+ assert(o);
+
+ if (o->object.type != OBJECT_ENTRY_ARRAY)
+ return 0;
+
+ return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
+}
+
+uint64_t journal_file_hash_table_n_items(Object *o) {
+ assert(o);
+
+ if (o->object.type != OBJECT_DATA_HASH_TABLE &&
+ o->object.type != OBJECT_FIELD_HASH_TABLE)
+ return 0;
+
+ return (le64toh(o->object.size) - offsetof(Object, hash_table.items)) / sizeof(HashItem);
+}
+
+static int link_entry_into_array(JournalFile *f,
+ le64_t *first,
+ le64_t *idx,
+ uint64_t p) {
+ int r;
+ uint64_t n = 0, ap = 0, q, i, a, hidx;
+ Object *o;
+
+ assert(f);
+ assert(first);
+ assert(idx);
+ assert(p > 0);
+
+ a = le64toh(*first);
+ i = hidx = le64toh(*idx);
+ while (a > 0) {
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
+ if (r < 0)
+ return r;
+
+ n = journal_file_entry_array_n_items(o);
+ if (i < n) {
+ o->entry_array.items[i] = htole64(p);
+ *idx = htole64(hidx + 1);
+ return 0;
+ }
+
+ i -= n;
+ ap = a;
+ a = le64toh(o->entry_array.next_entry_array_offset);
+ }
+
+ if (hidx > n)
+ n = (hidx+1) * 2;
+ else
+ n = n * 2;
+
+ if (n < 4)
+ n = 4;
+
+ r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
+ offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
+ &o, &q);
+ if (r < 0)
+ return r;
+
+#ifdef HAVE_GCRYPT
+ r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, o, q);
+ if (r < 0)
+ return r;
+#endif
+
+ o->entry_array.items[i] = htole64(p);
+
+ if (ap == 0)
+ *first = htole64(q);
+ else {
+ r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
+ if (r < 0)
+ return r;
+
+ o->entry_array.next_entry_array_offset = htole64(q);
+ }
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
+ f->header->n_entry_arrays = htole64(le64toh(f->header->n_entry_arrays) + 1);
+
+ *idx = htole64(hidx + 1);
+
+ return 0;
+}
+
+static int link_entry_into_array_plus_one(JournalFile *f,
+ le64_t *extra,
+ le64_t *first,
+ le64_t *idx,
+ uint64_t p) {
+
+ int r;
+
+ assert(f);
+ assert(extra);
+ assert(first);
+ assert(idx);
+ assert(p > 0);
+
+ if (*idx == 0)
+ *extra = htole64(p);
+ else {
+ le64_t i;
+
+ i = htole64(le64toh(*idx) - 1);
+ r = link_entry_into_array(f, first, &i, p);
+ if (r < 0)
+ return r;
+ }
+
+ *idx = htole64(le64toh(*idx) + 1);
+ return 0;
+}
+
+static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
+ uint64_t p;
+ int r;
+ assert(f);
+ assert(o);
+ assert(offset > 0);
+
+ p = le64toh(o->entry.items[i].object_offset);
+ if (p == 0)
+ return -EINVAL;
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+ if (r < 0)
+ return r;
+
+ return link_entry_into_array_plus_one(f,
+ &o->data.entry_offset,
+ &o->data.entry_array_offset,
+ &o->data.n_entries,
+ offset);
+}
+
+static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
+ uint64_t n, i;
+ int r;
+
+ assert(f);
+ assert(o);
+ assert(offset > 0);
+
+ if (o->object.type != OBJECT_ENTRY)
+ return -EINVAL;
+
+ __sync_synchronize();
+
+ /* Link up the entry itself */
+ r = link_entry_into_array(f,
+ &f->header->entry_array_offset,
+ &f->header->n_entries,
+ offset);
+ if (r < 0)
+ return r;
+
+ /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */
+
+ if (f->header->head_entry_realtime == 0)
+ f->header->head_entry_realtime = o->entry.realtime;
+
+ f->header->tail_entry_realtime = o->entry.realtime;
+ f->header->tail_entry_monotonic = o->entry.monotonic;
+
+ f->tail_entry_monotonic_valid = true;
+
+ /* Link up the items */
+ n = journal_file_entry_n_items(o);
+ for (i = 0; i < n; i++) {
+ r = journal_file_link_entry_item(f, o, offset, i);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int journal_file_append_entry_internal(
+ JournalFile *f,
+ const dual_timestamp *ts,
+ uint64_t xor_hash,
+ const EntryItem items[], unsigned n_items,
+ uint64_t *seqnum,
+ Object **ret, uint64_t *offset) {
+ uint64_t np;
+ uint64_t osize;
+ Object *o;
+ int r;
+
+ assert(f);
+ assert(items || n_items == 0);
+ assert(ts);
+
+ osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
+
+ r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
+ if (r < 0)
+ return r;
+
+ o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum));
+ memcpy(o->entry.items, items, n_items * sizeof(EntryItem));
+ o->entry.realtime = htole64(ts->realtime);
+ o->entry.monotonic = htole64(ts->monotonic);
+ o->entry.xor_hash = htole64(xor_hash);
+ o->entry.boot_id = f->header->boot_id;
+
+#ifdef HAVE_GCRYPT
+ r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np);
+ if (r < 0)
+ return r;
+#endif
+
+ r = journal_file_link_entry(f, o, np);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = np;
+
+ return 0;
+}
+
+void journal_file_post_change(JournalFile *f) {
+ assert(f);
+
+ /* inotify() does not receive IN_MODIFY events from file
+ * accesses done via mmap(). After each access we hence
+ * trigger IN_MODIFY by truncating the journal file to its
+ * current size which triggers IN_MODIFY. */
+
+ __sync_synchronize();
+
+ if (ftruncate(f->fd, f->last_stat.st_size) < 0)
+ log_error("Failed to truncate file to its own size: %m");
+}
+
+static int entry_item_cmp(const void *_a, const void *_b) {
+ const EntryItem *a = _a, *b = _b;
+
+ if (le64toh(a->object_offset) < le64toh(b->object_offset))
+ return -1;
+ if (le64toh(a->object_offset) > le64toh(b->object_offset))
+ return 1;
+ return 0;
+}
+
+int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqnum, Object **ret, uint64_t *offset) {
+ unsigned i;
+ EntryItem *items;
+ int r;
+ uint64_t xor_hash = 0;
+ struct dual_timestamp _ts;
+
+ assert(f);
+ assert(iovec || n_iovec == 0);
+
+ if (!f->writable)
+ return -EPERM;
+
+ if (!ts) {
+ dual_timestamp_get(&_ts);
+ ts = &_ts;
+ }
+
+ if (f->tail_entry_monotonic_valid &&
+ ts->monotonic < le64toh(f->header->tail_entry_monotonic))
+ return -EINVAL;
+
+#ifdef HAVE_GCRYPT
+ r = journal_file_maybe_append_tag(f, ts->realtime);
+ if (r < 0)
+ return r;
+#endif
+
+ /* alloca() can't take 0, hence let's allocate at least one */
+ items = alloca(sizeof(EntryItem) * MAX(1, n_iovec));
+
+ for (i = 0; i < n_iovec; i++) {
+ uint64_t p;
+ Object *o;
+
+ r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
+ if (r < 0)
+ return r;
+
+ xor_hash ^= le64toh(o->data.hash);
+ items[i].object_offset = htole64(p);
+ items[i].hash = o->data.hash;
+ }
+
+ /* Order by the position on disk, in order to improve seek
+ * times for rotating media. */
+ qsort(items, n_iovec, sizeof(EntryItem), entry_item_cmp);
+
+ r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
+
+ journal_file_post_change(f);
+
+ return r;
+}
+
+typedef struct ChainCacheItem {
+ uint64_t first; /* the array at the begin of the chain */
+ uint64_t array; /* the cached array */
+ uint64_t begin; /* the first item in the cached array */
+ uint64_t total; /* the total number of items in all arrays before this one in the chain */
+} ChainCacheItem;
+
+static void chain_cache_put(
+ Hashmap *h,
+ ChainCacheItem *ci,
+ uint64_t first,
+ uint64_t array,
+ uint64_t begin,
+ uint64_t total) {
+
+ if (!ci) {
+ /* If the chain item to cache for this chain is the
+ * first one it's not worth caching anything */
+ if (array == first)
+ return;
+
+ if (hashmap_size(h) >= CHAIN_CACHE_MAX)
+ ci = hashmap_steal_first(h);
+ else {
+ ci = new(ChainCacheItem, 1);
+ if (!ci)
+ return;
+ }
+
+ ci->first = first;
+
+ if (hashmap_put(h, &ci->first, ci) < 0) {
+ free(ci);
+ return;
+ }
+ } else
+ assert(ci->first == first);
+
+ ci->array = array;
+ ci->begin = begin;
+ ci->total = total;
+}
+
+static int generic_array_get(JournalFile *f,
+ uint64_t first,
+ uint64_t i,
+ Object **ret, uint64_t *offset) {
+
+ Object *o;
+ uint64_t p = 0, a, t = 0;
+ int r;
+ ChainCacheItem *ci;
+
+ assert(f);
+
+ a = first;
+
+ /* Try the chain cache first */
+ ci = hashmap_get(f->chain_cache, &first);
+ if (ci && i > ci->total) {
+ a = ci->array;
+ i -= ci->total;
+ t = ci->total;
+ }
+
+ while (a > 0) {
+ uint64_t k;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
+ if (r < 0)
+ return r;
+
+ k = journal_file_entry_array_n_items(o);
+ if (i < k) {
+ p = le64toh(o->entry_array.items[i]);
+ goto found;
+ }
+
+ i -= k;
+ t += k;
+ a = le64toh(o->entry_array.next_entry_array_offset);
+ }
+
+ return 0;
+
+found:
+ /* Let's cache this item for the next invocation */
+ chain_cache_put(f->chain_cache, ci, first, a, o->entry_array.items[0], t);
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = p;
+
+ return 1;
+}
+
+static int generic_array_get_plus_one(JournalFile *f,
+ uint64_t extra,
+ uint64_t first,
+ uint64_t i,
+ Object **ret, uint64_t *offset) {
+
+ Object *o;
+
+ assert(f);
+
+ if (i == 0) {
+ int r;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = extra;
+
+ return 1;
+ }
+
+ return generic_array_get(f, first, i-1, ret, offset);
+}
+
+enum {
+ TEST_FOUND,
+ TEST_LEFT,
+ TEST_RIGHT
+};
+
+static int generic_array_bisect(JournalFile *f,
+ uint64_t first,
+ uint64_t n,
+ uint64_t needle,
+ int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset,
+ uint64_t *idx) {
+
+ uint64_t a, p, t = 0, i = 0, last_p = 0;
+ bool subtract_one = false;
+ Object *o, *array = NULL;
+ int r;
+ ChainCacheItem *ci;
+
+ assert(f);
+ assert(test_object);
+
+ /* Start with the first array in the chain */
+ a = first;
+
+ ci = hashmap_get(f->chain_cache, &first);
+ if (ci && n > ci->total) {
+ /* Ah, we have iterated this bisection array chain
+ * previously! Let's see if we can skip ahead in the
+ * chain, as far as the last time. But we can't jump
+ * backwards in the chain, so let's check that
+ * first. */
+
+ r = test_object(f, ci->begin, needle);
+ if (r < 0)
+ return r;
+
+ if (r == TEST_LEFT) {
+ /* OK, what we are looking for is right of th
+ * begin of this EntryArray, so let's jump
+ * straight to previously cached array in the
+ * chain */
+
+ a = ci->array;
+ n -= ci->total;
+ t = ci->total;
+ }
+ }
+
+ while (a > 0) {
+ uint64_t left, right, k, lp;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
+ if (r < 0)
+ return r;
+
+ k = journal_file_entry_array_n_items(array);
+ right = MIN(k, n);
+ if (right <= 0)
+ return 0;
+
+ i = right - 1;
+ lp = p = le64toh(array->entry_array.items[i]);
+ if (p <= 0)
+ return -EBADMSG;
+
+ r = test_object(f, p, needle);
+ if (r < 0)
+ return r;
+
+ if (r == TEST_FOUND)
+ r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
+
+ if (r == TEST_RIGHT) {
+ left = 0;
+ right -= 1;
+ for (;;) {
+ if (left == right) {
+ if (direction == DIRECTION_UP)
+ subtract_one = true;
+
+ i = left;
+ goto found;
+ }
+
+ assert(left < right);
+
+ i = (left + right) / 2;
+ p = le64toh(array->entry_array.items[i]);
+ if (p <= 0)
+ return -EBADMSG;
+
+ r = test_object(f, p, needle);
+ if (r < 0)
+ return r;
+
+ if (r == TEST_FOUND)
+ r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
+
+ if (r == TEST_RIGHT)
+ right = i;
+ else
+ left = i + 1;
+ }
+ }
+
+ if (k > n) {
+ if (direction == DIRECTION_UP) {
+ i = n;
+ subtract_one = true;
+ goto found;
+ }
+
+ return 0;
+ }
+
+ last_p = lp;
+
+ n -= k;
+ t += k;
+ a = le64toh(array->entry_array.next_entry_array_offset);
+ }
+
+ return 0;
+
+found:
+ if (subtract_one && t == 0 && i == 0)
+ return 0;
+
+ /* Let's cache this item for the next invocation */
+ chain_cache_put(f->chain_cache, ci, first, a, array->entry_array.items[0], t);
+
+ if (subtract_one && i == 0)
+ p = last_p;
+ else if (subtract_one)
+ p = le64toh(array->entry_array.items[i-1]);
+ else
+ p = le64toh(array->entry_array.items[i]);
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = p;
+
+ if (idx)
+ *idx = t + i + (subtract_one ? -1 : 0);
+
+ return 1;
+}
+
+static int generic_array_bisect_plus_one(JournalFile *f,
+ uint64_t extra,
+ uint64_t first,
+ uint64_t n,
+ uint64_t needle,
+ int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset,
+ uint64_t *idx) {
+
+ int r;
+ bool step_back = false;
+ Object *o;
+
+ assert(f);
+ assert(test_object);
+
+ if (n <= 0)
+ return 0;
+
+ /* This bisects the array in object 'first', but first checks
+ * an extra */
+ r = test_object(f, extra, needle);
+ if (r < 0)
+ return r;
+
+ if (r == TEST_FOUND)
+ r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
+
+ /* if we are looking with DIRECTION_UP then we need to first
+ see if in the actual array there is a matching entry, and
+ return the last one of that. But if there isn't any we need
+ to return this one. Hence remember this, and return it
+ below. */
+ if (r == TEST_LEFT)
+ step_back = direction == DIRECTION_UP;
+
+ if (r == TEST_RIGHT) {
+ if (direction == DIRECTION_DOWN)
+ goto found;
+ else
+ return 0;
+ }
+
+ r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
+
+ if (r == 0 && step_back)
+ goto found;
+
+ if (r > 0 && idx)
+ (*idx) ++;
+
+ return r;
+
+found:
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = o;
+
+ if (offset)
+ *offset = extra;
+
+ if (idx)
+ *idx = 0;
+
+ return 1;
+}
+
+static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
+ assert(f);
+ assert(p > 0);
+
+ if (p == needle)
+ return TEST_FOUND;
+ else if (p < needle)
+ return TEST_LEFT;
+ else
+ return TEST_RIGHT;
+}
+
+int journal_file_move_to_entry_by_offset(
+ JournalFile *f,
+ uint64_t p,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ return generic_array_bisect(f,
+ le64toh(f->header->entry_array_offset),
+ le64toh(f->header->n_entries),
+ p,
+ test_object_offset,
+ direction,
+ ret, offset, NULL);
+}
+
+
+static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
+ Object *o;
+ int r;
+
+ assert(f);
+ assert(p > 0);
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+ if (r < 0)
+ return r;
+
+ if (le64toh(o->entry.seqnum) == needle)
+ return TEST_FOUND;
+ else if (le64toh(o->entry.seqnum) < needle)
+ return TEST_LEFT;
+ else
+ return TEST_RIGHT;
+}
+
+int journal_file_move_to_entry_by_seqnum(
+ JournalFile *f,
+ uint64_t seqnum,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ return generic_array_bisect(f,
+ le64toh(f->header->entry_array_offset),
+ le64toh(f->header->n_entries),
+ seqnum,
+ test_object_seqnum,
+ direction,
+ ret, offset, NULL);
+}
+
+static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
+ Object *o;
+ int r;
+
+ assert(f);
+ assert(p > 0);
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+ if (r < 0)
+ return r;
+
+ if (le64toh(o->entry.realtime) == needle)
+ return TEST_FOUND;
+ else if (le64toh(o->entry.realtime) < needle)
+ return TEST_LEFT;
+ else
+ return TEST_RIGHT;
+}
+
+int journal_file_move_to_entry_by_realtime(
+ JournalFile *f,
+ uint64_t realtime,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ return generic_array_bisect(f,
+ le64toh(f->header->entry_array_offset),
+ le64toh(f->header->n_entries),
+ realtime,
+ test_object_realtime,
+ direction,
+ ret, offset, NULL);
+}
+
+static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
+ Object *o;
+ int r;
+
+ assert(f);
+ assert(p > 0);
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+ if (r < 0)
+ return r;
+
+ if (le64toh(o->entry.monotonic) == needle)
+ return TEST_FOUND;
+ else if (le64toh(o->entry.monotonic) < needle)
+ return TEST_LEFT;
+ else
+ return TEST_RIGHT;
+}
+
+int journal_file_move_to_entry_by_monotonic(
+ JournalFile *f,
+ sd_id128_t boot_id,
+ uint64_t monotonic,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ char t[9+32+1] = "_BOOT_ID=";
+ Object *o;
+ int r;
+
+ assert(f);
+
+ sd_id128_to_string(boot_id, t + 9);
+ r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENOENT;
+
+ return generic_array_bisect_plus_one(f,
+ le64toh(o->data.entry_offset),
+ le64toh(o->data.entry_array_offset),
+ le64toh(o->data.n_entries),
+ monotonic,
+ test_object_monotonic,
+ direction,
+ ret, offset, NULL);
+}
+
+int journal_file_next_entry(
+ JournalFile *f,
+ Object *o, uint64_t p,
+ direction_t direction,
+ Object **ret, uint64_t *offset) {
+
+ uint64_t i, n;
+ int r;
+
+ assert(f);
+ assert(p > 0 || !o);
+
+ n = le64toh(f->header->n_entries);
+ if (n <= 0)
+ return 0;
+
+ if (!o)
+ i = direction == DIRECTION_DOWN ? 0 : n - 1;
+ else {
+ if (o->object.type != OBJECT_ENTRY)
+ return -EINVAL;
+
+ r = generic_array_bisect(f,
+ le64toh(f->header->entry_array_offset),
+ le64toh(f->header->n_entries),
+ p,
+ test_object_offset,
+ DIRECTION_DOWN,
+ NULL, NULL,
+ &i);
+ if (r <= 0)
+ return r;
+
+ if (direction == DIRECTION_DOWN) {
+ if (i >= n - 1)
+ return 0;
+
+ i++;
+ } else {
+ if (i <= 0)
+ return 0;
+
+ i--;
+ }
+ }
+
+ /* And jump to it */
+ return generic_array_get(f,
+ le64toh(f->header->entry_array_offset),
+ i,
+ ret, offset);
+}
+
+int journal_file_skip_entry(
+ JournalFile *f,
+ Object *o, uint64_t p,
+ int64_t skip,
+ Object **ret, uint64_t *offset) {
+
+ uint64_t i, n;
+ int r;
+
+ assert(f);
+ assert(o);
+ assert(p > 0);
+
+ if (o->object.type != OBJECT_ENTRY)
+ return -EINVAL;
+
+ r = generic_array_bisect(f,
+ le64toh(f->header->entry_array_offset),
+ le64toh(f->header->n_entries),
+ p,
+ test_object_offset,
+ DIRECTION_DOWN,
+ NULL, NULL,
+ &i);
+ if (r <= 0)
+ return r;
+
+ /* Calculate new index */
+ if (skip < 0) {
+ if ((uint64_t) -skip >= i)
+ i = 0;
+ else
+ i = i - (uint64_t) -skip;
+ } else
+ i += (uint64_t) skip;
+
+ n = le64toh(f->header->n_entries);
+ if (n <= 0)
+ return -EBADMSG;
+
+ if (i >= n)
+ i = n-1;
+
+ return generic_array_get(f,
+ le64toh(f->header->entry_array_offset),
+ i,
+ ret, offset);
+}
+
+int journal_file_next_entry_for_data(
+ JournalFile *f,
+ Object *o, uint64_t p,
+ uint64_t data_offset,
+ direction_t direction,
+ Object **ret, uint64_t *offset) {
+
+ uint64_t n, i;
+ int r;
+ Object *d;
+
+ assert(f);
+ assert(p > 0 || !o);
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
+ if (r < 0)
+ return r;
+
+ n = le64toh(d->data.n_entries);
+ if (n <= 0)
+ return n;
+
+ if (!o)
+ i = direction == DIRECTION_DOWN ? 0 : n - 1;
+ else {
+ if (o->object.type != OBJECT_ENTRY)
+ return -EINVAL;
+
+ r = generic_array_bisect_plus_one(f,
+ le64toh(d->data.entry_offset),
+ le64toh(d->data.entry_array_offset),
+ le64toh(d->data.n_entries),
+ p,
+ test_object_offset,
+ DIRECTION_DOWN,
+ NULL, NULL,
+ &i);
+
+ if (r <= 0)
+ return r;
+
+ if (direction == DIRECTION_DOWN) {
+ if (i >= n - 1)
+ return 0;
+
+ i++;
+ } else {
+ if (i <= 0)
+ return 0;
+
+ i--;
+ }
+
+ }
+
+ return generic_array_get_plus_one(f,
+ le64toh(d->data.entry_offset),
+ le64toh(d->data.entry_array_offset),
+ i,
+ ret, offset);
+}
+
+int journal_file_move_to_entry_by_offset_for_data(
+ JournalFile *f,
+ uint64_t data_offset,
+ uint64_t p,
+ direction_t direction,
+ Object **ret, uint64_t *offset) {
+
+ int r;
+ Object *d;
+
+ assert(f);
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
+ if (r < 0)
+ return r;
+
+ return generic_array_bisect_plus_one(f,
+ le64toh(d->data.entry_offset),
+ le64toh(d->data.entry_array_offset),
+ le64toh(d->data.n_entries),
+ p,
+ test_object_offset,
+ direction,
+ ret, offset, NULL);
+}
+
+int journal_file_move_to_entry_by_monotonic_for_data(
+ JournalFile *f,
+ uint64_t data_offset,
+ sd_id128_t boot_id,
+ uint64_t monotonic,
+ direction_t direction,
+ Object **ret, uint64_t *offset) {
+
+ char t[9+32+1] = "_BOOT_ID=";
+ Object *o, *d;
+ int r;
+ uint64_t b, z;
+
+ assert(f);
+
+ /* First, seek by time */
+ sd_id128_to_string(boot_id, t + 9);
+ r = journal_file_find_data_object(f, t, strlen(t), &o, &b);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENOENT;
+
+ r = generic_array_bisect_plus_one(f,
+ le64toh(o->data.entry_offset),
+ le64toh(o->data.entry_array_offset),
+ le64toh(o->data.n_entries),
+ monotonic,
+ test_object_monotonic,
+ direction,
+ NULL, &z, NULL);
+ if (r <= 0)
+ return r;
+
+ /* And now, continue seeking until we find an entry that
+ * exists in both bisection arrays */
+
+ for (;;) {
+ Object *qo;
+ uint64_t p, q;
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
+ if (r < 0)
+ return r;
+
+ r = generic_array_bisect_plus_one(f,
+ le64toh(d->data.entry_offset),
+ le64toh(d->data.entry_array_offset),
+ le64toh(d->data.n_entries),
+ z,
+ test_object_offset,
+ direction,
+ NULL, &p, NULL);
+ if (r <= 0)
+ return r;
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
+ if (r < 0)
+ return r;
+
+ r = generic_array_bisect_plus_one(f,
+ le64toh(o->data.entry_offset),
+ le64toh(o->data.entry_array_offset),
+ le64toh(o->data.n_entries),
+ p,
+ test_object_offset,
+ direction,
+ &qo, &q, NULL);
+
+ if (r <= 0)
+ return r;
+
+ if (p == q) {
+ if (ret)
+ *ret = qo;
+ if (offset)
+ *offset = q;
+
+ return 1;
+ }
+
+ z = q;
+ }
+
+ return 0;
+}
+
+int journal_file_move_to_entry_by_seqnum_for_data(
+ JournalFile *f,
+ uint64_t data_offset,
+ uint64_t seqnum,
+ direction_t direction,
+ Object **ret, uint64_t *offset) {
+
+ Object *d;
+ int r;
+
+ assert(f);
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
+ if (r < 0)
+ return r;
+
+ return generic_array_bisect_plus_one(f,
+ le64toh(d->data.entry_offset),
+ le64toh(d->data.entry_array_offset),
+ le64toh(d->data.n_entries),
+ seqnum,
+ test_object_seqnum,
+ direction,
+ ret, offset, NULL);
+}
+
+int journal_file_move_to_entry_by_realtime_for_data(
+ JournalFile *f,
+ uint64_t data_offset,
+ uint64_t realtime,
+ direction_t direction,
+ Object **ret, uint64_t *offset) {
+
+ Object *d;
+ int r;
+
+ assert(f);
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
+ if (r < 0)
+ return r;
+
+ return generic_array_bisect_plus_one(f,
+ le64toh(d->data.entry_offset),
+ le64toh(d->data.entry_array_offset),
+ le64toh(d->data.n_entries),
+ realtime,
+ test_object_realtime,
+ direction,
+ ret, offset, NULL);
+}
+
+void journal_file_dump(JournalFile *f) {
+ Object *o;
+ int r;
+ uint64_t p;
+
+ assert(f);
+
+ journal_file_print_header(f);
+
+ p = le64toh(f->header->header_size);
+ while (p != 0) {
+ r = journal_file_move_to_object(f, -1, p, &o);
+ if (r < 0)
+ goto fail;
+
+ switch (o->object.type) {
+
+ case OBJECT_UNUSED:
+ printf("Type: OBJECT_UNUSED\n");
+ break;
+
+ case OBJECT_DATA:
+ printf("Type: OBJECT_DATA\n");
+ break;
+
+ case OBJECT_FIELD:
+ printf("Type: OBJECT_FIELD\n");
+ break;
+
+ case OBJECT_ENTRY:
+ printf("Type: OBJECT_ENTRY seqnum=%llu monotonic=%llu realtime=%llu\n",
+ (unsigned long long) le64toh(o->entry.seqnum),
+ (unsigned long long) le64toh(o->entry.monotonic),
+ (unsigned long long) le64toh(o->entry.realtime));
+ break;
+
+ case OBJECT_FIELD_HASH_TABLE:
+ printf("Type: OBJECT_FIELD_HASH_TABLE\n");
+ break;
+
+ case OBJECT_DATA_HASH_TABLE:
+ printf("Type: OBJECT_DATA_HASH_TABLE\n");
+ break;
+
+ case OBJECT_ENTRY_ARRAY:
+ printf("Type: OBJECT_ENTRY_ARRAY\n");
+ break;
+
+ case OBJECT_TAG:
+ printf("Type: OBJECT_TAG seqnum=%llu epoch=%llu\n",
+ (unsigned long long) le64toh(o->tag.seqnum),
+ (unsigned long long) le64toh(o->tag.epoch));
+ break;
+
+ default:
+ printf("Type: unknown (%u)\n", o->object.type);
+ break;
+ }
+
+ if (o->object.flags & OBJECT_COMPRESSED)
+ printf("Flags: COMPRESSED\n");
+
+ if (p == le64toh(f->header->tail_object_offset))
+ p = 0;
+ else
+ p = p + ALIGN64(le64toh(o->object.size));
+ }
+
+ return;
+fail:
+ log_error("File corrupt");
+}
+
+void journal_file_print_header(JournalFile *f) {
+ char a[33], b[33], c[33];
+ char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX];
+ struct stat st;
+ char bytes[FORMAT_BYTES_MAX];
+
+ assert(f);
+
+ printf("File Path: %s\n"
+ "File ID: %s\n"
+ "Machine ID: %s\n"
+ "Boot ID: %s\n"
+ "Sequential Number ID: %s\n"
+ "State: %s\n"
+ "Compatible Flags:%s%s\n"
+ "Incompatible Flags:%s%s\n"
+ "Header size: %llu\n"
+ "Arena size: %llu\n"
+ "Data Hash Table Size: %llu\n"
+ "Field Hash Table Size: %llu\n"
+ "Rotate Suggested: %s\n"
+ "Head Sequential Number: %llu\n"
+ "Tail Sequential Number: %llu\n"
+ "Head Realtime Timestamp: %s\n"
+ "Tail Realtime Timestamp: %s\n"
+ "Objects: %llu\n"
+ "Entry Objects: %llu\n",
+ f->path,
+ sd_id128_to_string(f->header->file_id, a),
+ sd_id128_to_string(f->header->machine_id, b),
+ sd_id128_to_string(f->header->boot_id, c),
+ sd_id128_to_string(f->header->seqnum_id, c),
+ f->header->state == STATE_OFFLINE ? "OFFLINE" :
+ f->header->state == STATE_ONLINE ? "ONLINE" :
+ f->header->state == STATE_ARCHIVED ? "ARCHIVED" : "UNKNOWN",
+ JOURNAL_HEADER_SEALED(f->header) ? " SEALED" : "",
+ (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SEALED) ? " ???" : "",
+ JOURNAL_HEADER_COMPRESSED(f->header) ? " COMPRESSED" : "",
+ (le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) ? " ???" : "",
+ (unsigned long long) le64toh(f->header->header_size),
+ (unsigned long long) le64toh(f->header->arena_size),
+ (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
+ (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
+ yes_no(journal_file_rotate_suggested(f, 0)),
+ (unsigned long long) le64toh(f->header->head_entry_seqnum),
+ (unsigned long long) le64toh(f->header->tail_entry_seqnum),
+ format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
+ format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)),
+ (unsigned long long) le64toh(f->header->n_objects),
+ (unsigned long long) le64toh(f->header->n_entries));
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
+ printf("Data Objects: %llu\n"
+ "Data Hash Table Fill: %.1f%%\n",
+ (unsigned long long) le64toh(f->header->n_data),
+ 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
+ printf("Field Objects: %llu\n"
+ "Field Hash Table Fill: %.1f%%\n",
+ (unsigned long long) le64toh(f->header->n_fields),
+ 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_tags))
+ printf("Tag Objects: %llu\n",
+ (unsigned long long) le64toh(f->header->n_tags));
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
+ printf("Entry Array Objects: %llu\n",
+ (unsigned long long) le64toh(f->header->n_entry_arrays));
+
+ if (fstat(f->fd, &st) >= 0)
+ printf("Disk usage: %s\n", format_bytes(bytes, sizeof(bytes), (off_t) st.st_blocks * 512ULL));
+}
+
+int journal_file_open(
+ const char *fname,
+ int flags,
+ mode_t mode,
+ bool compress,
+ bool seal,
+ JournalMetrics *metrics,
+ MMapCache *mmap_cache,
+ JournalFile *template,
+ JournalFile **ret) {
+
+ JournalFile *f;
+ int r;
+ bool newly_created = false;
+
+ assert(fname);
+ assert(ret);
+
+ if ((flags & O_ACCMODE) != O_RDONLY &&
+ (flags & O_ACCMODE) != O_RDWR)
+ return -EINVAL;
+
+ if (!endswith(fname, ".journal") &&
+ !endswith(fname, ".journal~"))
+ return -EINVAL;
+
+ f = new0(JournalFile, 1);
+ if (!f)
+ return -ENOMEM;
+
+ f->fd = -1;
+ f->mode = mode;
+
+ f->flags = flags;
+ f->prot = prot_from_flags(flags);
+ f->writable = (flags & O_ACCMODE) != O_RDONLY;
+#ifdef HAVE_XZ
+ f->compress = compress;
+#endif
+#ifdef HAVE_GCRYPT
+ f->seal = seal;
+#endif
+
+ if (mmap_cache)
+ f->mmap = mmap_cache_ref(mmap_cache);
+ else {
+ f->mmap = mmap_cache_new();
+ if (!f->mmap) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ f->path = strdup(fname);
+ if (!f->path) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ f->chain_cache = hashmap_new(uint64_hash_func, uint64_compare_func);
+ if (!f->chain_cache) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
+ if (f->fd < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (fstat(f->fd, &f->last_stat) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (f->last_stat.st_size == 0 && f->writable) {
+#ifdef HAVE_XATTR
+ uint64_t crtime;
+
+ /* Let's attach the creation time to the journal file,
+ * so that the vacuuming code knows the age of this
+ * file even if the file might end up corrupted one
+ * day... Ideally we'd just use the creation time many
+ * file systems maintain for each file, but there is
+ * currently no usable API to query this, hence let's
+ * emulate this via extended attributes. If extended
+ * attributes are not supported we'll just skip this,
+ * and rely solely on mtime/atime/ctime of the file.*/
+
+ crtime = htole64((uint64_t) now(CLOCK_REALTIME));
+ fsetxattr(f->fd, "user.crtime_usec", &crtime, sizeof(crtime), XATTR_CREATE);
+#endif
+
+#ifdef HAVE_GCRYPT
+ /* Try to load the FSPRG state, and if we can't, then
+ * just don't do sealing */
+ if (f->seal) {
+ r = journal_file_fss_load(f);
+ if (r < 0)
+ f->seal = false;
+ }
+#endif
+
+ r = journal_file_init_header(f, template);
+ if (r < 0)
+ goto fail;
+
+ if (fstat(f->fd, &f->last_stat) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ newly_created = true;
+ }
+
+ if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
+ r = -EIO;
+ goto fail;
+ }
+
+ f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
+ if (f->header == MAP_FAILED) {
+ f->header = NULL;
+ r = -errno;
+ goto fail;
+ }
+
+ if (!newly_created) {
+ r = journal_file_verify_header(f);
+ if (r < 0)
+ goto fail;
+ }
+
+#ifdef HAVE_GCRYPT
+ if (!newly_created && f->writable) {
+ r = journal_file_fss_load(f);
+ if (r < 0)
+ goto fail;
+ }
+#endif
+
+ if (f->writable) {
+ if (metrics) {
+ journal_default_metrics(metrics, f->fd);
+ f->metrics = *metrics;
+ } else if (template)
+ f->metrics = template->metrics;
+
+ r = journal_file_refresh_header(f);
+ if (r < 0)
+ goto fail;
+ }
+
+#ifdef HAVE_GCRYPT
+ r = journal_file_hmac_setup(f);
+ if (r < 0)
+ goto fail;
+#endif
+
+ if (newly_created) {
+ r = journal_file_setup_field_hash_table(f);
+ if (r < 0)
+ goto fail;
+
+ r = journal_file_setup_data_hash_table(f);
+ if (r < 0)
+ goto fail;
+
+#ifdef HAVE_GCRYPT
+ r = journal_file_append_first_tag(f);
+ if (r < 0)
+ goto fail;
+#endif
+ }
+
+ r = journal_file_map_field_hash_table(f);
+ if (r < 0)
+ goto fail;
+
+ r = journal_file_map_data_hash_table(f);
+ if (r < 0)
+ goto fail;
+
+ *ret = f;
+ return 0;
+
+fail:
+ journal_file_close(f);
+
+ return r;
+}
+
+int journal_file_rotate(JournalFile **f, bool compress, bool seal) {
+ char *p;
+ size_t l;
+ JournalFile *old_file, *new_file = NULL;
+ int r;
+
+ assert(f);
+ assert(*f);
+
+ old_file = *f;
+
+ if (!old_file->writable)
+ return -EINVAL;
+
+ if (!endswith(old_file->path, ".journal"))
+ return -EINVAL;
+
+ l = strlen(old_file->path);
+
+ p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
+ if (!p)
+ return -ENOMEM;
+
+ memcpy(p, old_file->path, l - 8);
+ p[l-8] = '@';
+ sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
+ snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
+ "-%016llx-%016llx.journal",
+ (unsigned long long) le64toh((*f)->header->head_entry_seqnum),
+ (unsigned long long) le64toh((*f)->header->head_entry_realtime));
+
+ r = rename(old_file->path, p);
+ free(p);
+
+ if (r < 0)
+ return -errno;
+
+ old_file->header->state = STATE_ARCHIVED;
+
+ r = journal_file_open(old_file->path, old_file->flags, old_file->mode, compress, seal, NULL, old_file->mmap, old_file, &new_file);
+ journal_file_close(old_file);
+
+ *f = new_file;
+ return r;
+}
+
+int journal_file_open_reliably(
+ const char *fname,
+ int flags,
+ mode_t mode,
+ bool compress,
+ bool seal,
+ JournalMetrics *metrics,
+ MMapCache *mmap_cache,
+ JournalFile *template,
+ JournalFile **ret) {
+
+ int r;
+ size_t l;
+ char *p;
+
+ r = journal_file_open(fname, flags, mode, compress, seal,
+ metrics, mmap_cache, template, ret);
+ if (r != -EBADMSG && /* corrupted */
+ r != -ENODATA && /* truncated */
+ r != -EHOSTDOWN && /* other machine */
+ r != -EPROTONOSUPPORT && /* incompatible feature */
+ r != -EBUSY && /* unclean shutdown */
+ r != -ESHUTDOWN /* already archived */)
+ return r;
+
+ if ((flags & O_ACCMODE) == O_RDONLY)
+ return r;
+
+ if (!(flags & O_CREAT))
+ return r;
+
+ if (!endswith(fname, ".journal"))
+ return r;
+
+ /* The file is corrupted. Rotate it away and try it again (but only once) */
+
+ l = strlen(fname);
+ if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
+ (int) (l-8), fname,
+ (unsigned long long) now(CLOCK_REALTIME),
+ random_ull()) < 0)
+ return -ENOMEM;
+
+ r = rename(fname, p);
+ free(p);
+ if (r < 0)
+ return -errno;
+
+ log_warning("File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
+
+ return journal_file_open(fname, flags, mode, compress, seal,
+ metrics, mmap_cache, template, ret);
+}
+
+
+int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
+ uint64_t i, n;
+ uint64_t q, xor_hash = 0;
+ int r;
+ EntryItem *items;
+ dual_timestamp ts;
+
+ assert(from);
+ assert(to);
+ assert(o);
+ assert(p);
+
+ if (!to->writable)
+ return -EPERM;
+
+ ts.monotonic = le64toh(o->entry.monotonic);
+ ts.realtime = le64toh(o->entry.realtime);
+
+ if (to->tail_entry_monotonic_valid &&
+ ts.monotonic < le64toh(to->header->tail_entry_monotonic))
+ return -EINVAL;
+
+ n = journal_file_entry_n_items(o);
+ items = alloca(sizeof(EntryItem) * n);
+
+ for (i = 0; i < n; i++) {
+ uint64_t l, h;
+ le64_t le_hash;
+ size_t t;
+ void *data;
+ Object *u;
+
+ q = le64toh(o->entry.items[i].object_offset);
+ le_hash = o->entry.items[i].hash;
+
+ r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
+ if (r < 0)
+ return r;
+
+ if (le_hash != o->data.hash)
+ return -EBADMSG;
+
+ l = le64toh(o->object.size) - offsetof(Object, data.payload);
+ t = (size_t) l;
+
+ /* We hit the limit on 32bit machines */
+ if ((uint64_t) t != l)
+ return -E2BIG;
+
+ if (o->object.flags & OBJECT_COMPRESSED) {
+#ifdef HAVE_XZ
+ uint64_t rsize;
+
+ if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
+ return -EBADMSG;
+
+ data = from->compress_buffer;
+ l = rsize;
+#else
+ return -EPROTONOSUPPORT;
+#endif
+ } else
+ data = o->data.payload;
+
+ r = journal_file_append_data(to, data, l, &u, &h);
+ if (r < 0)
+ return r;
+
+ xor_hash ^= le64toh(u->data.hash);
+ items[i].object_offset = htole64(h);
+ items[i].hash = u->data.hash;
+
+ r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
+ if (r < 0)
+ return r;
+ }
+
+ return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
+}
+
+void journal_default_metrics(JournalMetrics *m, int fd) {
+ uint64_t fs_size = 0;
+ struct statvfs ss;
+ char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
+
+ assert(m);
+ assert(fd >= 0);
+
+ if (fstatvfs(fd, &ss) >= 0)
+ fs_size = ss.f_frsize * ss.f_blocks;
+
+ if (m->max_use == (uint64_t) -1) {
+
+ if (fs_size > 0) {
+ m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
+
+ if (m->max_use > DEFAULT_MAX_USE_UPPER)
+ m->max_use = DEFAULT_MAX_USE_UPPER;
+
+ if (m->max_use < DEFAULT_MAX_USE_LOWER)
+ m->max_use = DEFAULT_MAX_USE_LOWER;
+ } else
+ m->max_use = DEFAULT_MAX_USE_LOWER;
+ } else {
+ m->max_use = PAGE_ALIGN(m->max_use);
+
+ if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
+ m->max_use = JOURNAL_FILE_SIZE_MIN*2;
+ }
+
+ if (m->max_size == (uint64_t) -1) {
+ m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
+
+ if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
+ m->max_size = DEFAULT_MAX_SIZE_UPPER;
+ } else
+ m->max_size = PAGE_ALIGN(m->max_size);
+
+ if (m->max_size < JOURNAL_FILE_SIZE_MIN)
+ m->max_size = JOURNAL_FILE_SIZE_MIN;
+
+ if (m->max_size*2 > m->max_use)
+ m->max_use = m->max_size*2;
+
+ if (m->min_size == (uint64_t) -1)
+ m->min_size = JOURNAL_FILE_SIZE_MIN;
+ else {
+ m->min_size = PAGE_ALIGN(m->min_size);
+
+ if (m->min_size < JOURNAL_FILE_SIZE_MIN)
+ m->min_size = JOURNAL_FILE_SIZE_MIN;
+
+ if (m->min_size > m->max_size)
+ m->max_size = m->min_size;
+ }
+
+ if (m->keep_free == (uint64_t) -1) {
+
+ if (fs_size > 0) {
+ m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
+
+ if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
+ m->keep_free = DEFAULT_KEEP_FREE_UPPER;
+
+ } else
+ m->keep_free = DEFAULT_KEEP_FREE;
+ }
+
+ log_debug("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
+ format_bytes(a, sizeof(a), m->max_use),
+ format_bytes(b, sizeof(b), m->max_size),
+ format_bytes(c, sizeof(c), m->min_size),
+ format_bytes(d, sizeof(d), m->keep_free));
+}
+
+int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
+ assert(f);
+ assert(from || to);
+
+ if (from) {
+ if (f->header->head_entry_realtime == 0)
+ return -ENOENT;
+
+ *from = le64toh(f->header->head_entry_realtime);
+ }
+
+ if (to) {
+ if (f->header->tail_entry_realtime == 0)
+ return -ENOENT;
+
+ *to = le64toh(f->header->tail_entry_realtime);
+ }
+
+ return 1;
+}
+
+int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
+ char t[9+32+1] = "_BOOT_ID=";
+ Object *o;
+ uint64_t p;
+ int r;
+
+ assert(f);
+ assert(from || to);
+
+ sd_id128_to_string(boot_id, t + 9);
+
+ r = journal_file_find_data_object(f, t, strlen(t), &o, &p);
+ if (r <= 0)
+ return r;
+
+ if (le64toh(o->data.n_entries) <= 0)
+ return 0;
+
+ if (from) {
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
+ if (r < 0)
+ return r;
+
+ *from = le64toh(o->entry.monotonic);
+ }
+
+ if (to) {
+ r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+ if (r < 0)
+ return r;
+
+ r = generic_array_get_plus_one(f,
+ le64toh(o->data.entry_offset),
+ le64toh(o->data.entry_array_offset),
+ le64toh(o->data.n_entries)-1,
+ &o, NULL);
+ if (r <= 0)
+ return r;
+
+ *to = le64toh(o->entry.monotonic);
+ }
+
+ return 1;
+}
+
+bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec) {
+ assert(f);
+
+ /* If we gained new header fields we gained new features,
+ * hence suggest a rotation */
+ if (le64toh(f->header->header_size) < sizeof(Header)) {
+ log_debug("%s uses an outdated header, suggesting rotation.", f->path);
+ return true;
+ }
+
+ /* Let's check if the hash tables grew over a certain fill
+ * level (75%, borrowing this value from Java's hash table
+ * implementation), and if so suggest a rotation. To calculate
+ * the fill level we need the n_data field, which only exists
+ * in newer versions. */
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
+ if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
+ log_debug("Data hash table of %s has a fill level at %.1f (%llu of %llu items, %llu file size, %llu bytes per hash table item), suggesting rotation.",
+ f->path,
+ 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
+ (unsigned long long) le64toh(f->header->n_data),
+ (unsigned long long) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)),
+ (unsigned long long) (f->last_stat.st_size),
+ (unsigned long long) (f->last_stat.st_size / le64toh(f->header->n_data)));
+ return true;
+ }
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
+ if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
+ log_debug("Field hash table of %s has a fill level at %.1f (%llu of %llu items), suggesting rotation.",
+ f->path,
+ 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
+ (unsigned long long) le64toh(f->header->n_fields),
+ (unsigned long long) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)));
+ return true;
+ }
+
+ /* Are the data objects properly indexed by field objects? */
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
+ JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
+ le64toh(f->header->n_data) > 0 &&
+ le64toh(f->header->n_fields) == 0)
+ return true;
+
+ if (max_file_usec > 0) {
+ usec_t t, h;
+
+ h = le64toh(f->header->head_entry_realtime);
+ t = now(CLOCK_REALTIME);
+
+ if (h > 0 && t > h + max_file_usec)
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h
new file mode 100644
index 0000000000..cdbc8e41f6
--- /dev/null
+++ b/src/journal/journal-file.h
@@ -0,0 +1,193 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#ifdef HAVE_GCRYPT
+#include <gcrypt.h>
+#endif
+
+#include <systemd/sd-id128.h>
+
+#include "sparse-endian.h"
+#include "journal-def.h"
+#include "util.h"
+#include "mmap-cache.h"
+#include "hashmap.h"
+
+typedef struct JournalMetrics {
+ uint64_t max_use;
+ uint64_t max_size;
+ uint64_t min_size;
+ uint64_t keep_free;
+} JournalMetrics;
+
+typedef struct JournalFile {
+ int fd;
+ char *path;
+ struct stat last_stat;
+ mode_t mode;
+
+ int flags;
+ int prot;
+ bool writable;
+ bool compress;
+ bool seal;
+
+ bool tail_entry_monotonic_valid;
+
+ Header *header;
+ HashItem *data_hash_table;
+ HashItem *field_hash_table;
+
+ uint64_t current_offset;
+
+ JournalMetrics metrics;
+ MMapCache *mmap;
+
+ Hashmap *chain_cache;
+
+#ifdef HAVE_XZ
+ void *compress_buffer;
+ uint64_t compress_buffer_size;
+#endif
+
+#ifdef HAVE_GCRYPT
+ gcry_md_hd_t hmac;
+ bool hmac_running;
+
+ FSSHeader *fss_file;
+ size_t fss_file_size;
+
+ uint64_t fss_start_usec;
+ uint64_t fss_interval_usec;
+
+ void *fsprg_state;
+ size_t fsprg_state_size;
+
+ void *fsprg_seed;
+ size_t fsprg_seed_size;
+#endif
+} JournalFile;
+
+typedef enum direction {
+ DIRECTION_UP,
+ DIRECTION_DOWN
+} direction_t;
+
+int journal_file_open(
+ const char *fname,
+ int flags,
+ mode_t mode,
+ bool compress,
+ bool seal,
+ JournalMetrics *metrics,
+ MMapCache *mmap_cache,
+ JournalFile *template,
+ JournalFile **ret);
+
+void journal_file_close(JournalFile *j);
+
+int journal_file_open_reliably(
+ const char *fname,
+ int flags,
+ mode_t mode,
+ bool compress,
+ bool seal,
+ JournalMetrics *metrics,
+ MMapCache *mmap_cache,
+ JournalFile *template,
+ JournalFile **ret);
+
+#define ALIGN64(x) (((x) + 7ULL) & ~7ULL)
+#define VALID64(x) (((x) & 7ULL) == 0ULL)
+
+static inline bool VALID_REALTIME(uint64_t u) {
+ /* This considers timestamps until the year 3112 valid. That should be plenty room... */
+ return u > 0 && u < (1ULL << 55);
+}
+
+static inline bool VALID_MONOTONIC(uint64_t u) {
+ /* This considers timestamps until 1142 years of runtime valid. */
+ return u < (1ULL << 55);
+}
+
+static inline bool VALID_EPOCH(uint64_t u) {
+ /* This allows changing the key for 1142 years, every usec. */
+ return u < (1ULL << 55);
+}
+
+#define JOURNAL_HEADER_CONTAINS(h, field) \
+ (le64toh((h)->header_size) >= offsetof(Header, field) + sizeof((h)->field))
+
+#define JOURNAL_HEADER_SEALED(h) \
+ (!!(le32toh((h)->compatible_flags) & HEADER_COMPATIBLE_SEALED))
+
+#define JOURNAL_HEADER_COMPRESSED(h) \
+ (!!(le32toh((h)->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED))
+
+int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret);
+
+uint64_t journal_file_entry_n_items(Object *o);
+uint64_t journal_file_entry_array_n_items(Object *o);
+uint64_t journal_file_hash_table_n_items(Object *o);
+
+int journal_file_append_object(JournalFile *f, int type, uint64_t size, Object **ret, uint64_t *offset);
+int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqno, Object **ret, uint64_t *offset);
+
+int journal_file_find_data_object(JournalFile *f, const void *data, uint64_t size, Object **ret, uint64_t *offset);
+int journal_file_find_data_object_with_hash(JournalFile *f, const void *data, uint64_t size, uint64_t hash, Object **ret, uint64_t *offset);
+
+int journal_file_find_field_object(JournalFile *f, const void *field, uint64_t size, Object **ret, uint64_t *offset);
+int journal_file_find_field_object_with_hash(JournalFile *f, const void *field, uint64_t size, uint64_t hash, Object **ret, uint64_t *offset);
+
+int journal_file_next_entry(JournalFile *f, Object *o, uint64_t p, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_skip_entry(JournalFile *f, Object *o, uint64_t p, int64_t skip, Object **ret, uint64_t *offset);
+
+int journal_file_next_entry_for_data(JournalFile *f, Object *o, uint64_t p, uint64_t data_offset, direction_t direction, Object **ret, uint64_t *offset);
+
+int journal_file_move_to_entry_by_offset(JournalFile *f, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_move_to_entry_by_seqnum(JournalFile *f, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_move_to_entry_by_realtime(JournalFile *f, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_move_to_entry_by_monotonic(JournalFile *f, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset);
+
+int journal_file_move_to_entry_by_offset_for_data(JournalFile *f, uint64_t data_offset, uint64_t p, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_move_to_entry_by_seqnum_for_data(JournalFile *f, uint64_t data_offset, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_move_to_entry_by_realtime_for_data(JournalFile *f, uint64_t data_offset, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset);
+int journal_file_move_to_entry_by_monotonic_for_data(JournalFile *f, uint64_t data_offset, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset);
+
+int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset);
+
+void journal_file_dump(JournalFile *f);
+void journal_file_print_header(JournalFile *f);
+
+int journal_file_rotate(JournalFile **f, bool compress, bool seal);
+
+void journal_file_post_change(JournalFile *f);
+
+void journal_default_metrics(JournalMetrics *m, int fd);
+
+int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to);
+int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot, usec_t *from, usec_t *to);
+
+bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec);
diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
new file mode 100644
index 0000000000..35462795c2
--- /dev/null
+++ b/src/journal/journal-gatewayd.c
@@ -0,0 +1,916 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <microhttpd.h>
+
+#include "log.h"
+#include "util.h"
+#include "sd-journal.h"
+#include "sd-daemon.h"
+#include "logs-show.h"
+#include "virt.h"
+
+typedef struct RequestMeta {
+ sd_journal *journal;
+
+ OutputMode mode;
+
+ char *cursor;
+ int64_t n_skip;
+ uint64_t n_entries;
+ bool n_entries_set;
+
+ FILE *tmp;
+ uint64_t delta, size;
+
+ int argument_parse_error;
+
+ bool follow;
+ bool discrete;
+
+ uint64_t n_fields;
+ bool n_fields_set;
+} RequestMeta;
+
+static const char* const mime_types[_OUTPUT_MODE_MAX] = {
+ [OUTPUT_SHORT] = "text/plain",
+ [OUTPUT_JSON] = "application/json",
+ [OUTPUT_JSON_SSE] = "text/event-stream",
+ [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
+};
+
+static RequestMeta *request_meta(void **connection_cls) {
+ RequestMeta *m;
+
+ if (*connection_cls)
+ return *connection_cls;
+
+ m = new0(RequestMeta, 1);
+ if (!m)
+ return NULL;
+
+ *connection_cls = m;
+ return m;
+}
+
+static void request_meta_free(
+ void *cls,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ enum MHD_RequestTerminationCode toe) {
+
+ RequestMeta *m = *connection_cls;
+
+ if (!m)
+ return;
+
+ if (m->journal)
+ sd_journal_close(m->journal);
+
+ if (m->tmp)
+ fclose(m->tmp);
+
+ free(m->cursor);
+ free(m);
+}
+
+static int open_journal(RequestMeta *m) {
+ assert(m);
+
+ if (m->journal)
+ return 0;
+
+ return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
+}
+
+
+static int respond_oom(struct MHD_Connection *connection) {
+ struct MHD_Response *response;
+ const char m[] = "Out of memory.\n";
+ int ret;
+
+ assert(connection);
+
+ response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
+ if (!response)
+ return MHD_NO;
+
+ MHD_add_response_header(response, "Content-Type", "text/plain");
+ ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
+ MHD_destroy_response(response);
+
+ return ret;
+}
+
+static int respond_error(
+ struct MHD_Connection *connection,
+ unsigned code,
+ const char *format, ...) {
+
+ struct MHD_Response *response;
+ char *m;
+ int r;
+ va_list ap;
+
+ assert(connection);
+ assert(format);
+
+ va_start(ap, format);
+ r = vasprintf(&m, format, ap);
+ va_end(ap);
+
+ if (r < 0)
+ return respond_oom(connection);
+
+ response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
+ if (!response) {
+ free(m);
+ return respond_oom(connection);
+ }
+
+ MHD_add_response_header(response, "Content-Type", "text/plain");
+ r = MHD_queue_response(connection, code, response);
+ MHD_destroy_response(response);
+
+ return r;
+}
+
+static ssize_t request_reader_entries(
+ void *cls,
+ uint64_t pos,
+ char *buf,
+ size_t max) {
+
+ RequestMeta *m = cls;
+ int r;
+ size_t n, k;
+
+ assert(m);
+ assert(buf);
+ assert(max > 0);
+ assert(pos >= m->delta);
+
+ pos -= m->delta;
+
+ while (pos >= m->size) {
+ off_t sz;
+
+ /* End of this entry, so let's serialize the next
+ * one */
+
+ if (m->n_entries_set &&
+ m->n_entries <= 0)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+
+ if (m->n_skip < 0)
+ r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
+ else if (m->n_skip > 0)
+ r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
+ else
+ r = sd_journal_next(m->journal);
+
+ if (r < 0) {
+ log_error("Failed to advance journal pointer: %s", strerror(-r));
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ } else if (r == 0) {
+
+ if (m->follow) {
+ r = sd_journal_wait(m->journal, (uint64_t) -1);
+ if (r < 0) {
+ log_error("Couldn't wait for journal event: %s", strerror(-r));
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ continue;
+ }
+
+ 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;
+
+ if (m->n_entries_set)
+ m->n_entries -= 1;
+
+ m->n_skip = 0;
+
+ if (m->tmp)
+ rewind(m->tmp);
+ else {
+ m->tmp = tmpfile();
+ if (!m->tmp) {
+ log_error("Failed to create temporary file: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+ }
+
+ r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
+ if (r < 0) {
+ log_error("Failed to serialize item: %s", strerror(-r));
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ sz = ftello(m->tmp);
+ if (sz == (off_t) -1) {
+ log_error("Failed to retrieve file position: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ m->size = (uint64_t) sz;
+ }
+
+ if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
+ log_error("Failed to seek to position: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ n = m->size - pos;
+ if (n > max)
+ n = max;
+
+ errno = 0;
+ k = fread(buf, 1, n, m->tmp);
+ if (k != n) {
+ log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ return (ssize_t) k;
+}
+
+static int request_parse_accept(
+ RequestMeta *m,
+ struct MHD_Connection *connection) {
+
+ const char *header;
+
+ assert(m);
+ assert(connection);
+
+ header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
+ if (!header)
+ return 0;
+
+ if (streq(header, mime_types[OUTPUT_JSON]))
+ m->mode = OUTPUT_JSON;
+ else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
+ m->mode = OUTPUT_JSON_SSE;
+ else if (streq(header, mime_types[OUTPUT_EXPORT]))
+ m->mode = OUTPUT_EXPORT;
+ else
+ m->mode = OUTPUT_SHORT;
+
+ return 0;
+}
+
+static int request_parse_range(
+ RequestMeta *m,
+ struct MHD_Connection *connection) {
+
+ const char *range, *colon, *colon2;
+ int r;
+
+ assert(m);
+ assert(connection);
+
+ range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
+ if (!range)
+ return 0;
+
+ if (!startswith(range, "entries="))
+ return 0;
+
+ range += 8;
+ range += strspn(range, WHITESPACE);
+
+ colon = strchr(range, ':');
+ if (!colon)
+ m->cursor = strdup(range);
+ else {
+ const char *p;
+
+ colon2 = strchr(colon + 1, ':');
+ if (colon2) {
+ char *t;
+
+ t = strndup(colon + 1, colon2 - colon - 1);
+ if (!t)
+ return -ENOMEM;
+
+ r = safe_atoi64(t, &m->n_skip);
+ free(t);
+ if (r < 0)
+ return r;
+ }
+
+ p = (colon2 ? colon2 : colon) + 1;
+ if (*p) {
+ r = safe_atou64(p, &m->n_entries);
+ if (r < 0)
+ return r;
+
+ if (m->n_entries <= 0)
+ return -EINVAL;
+
+ m->n_entries_set = true;
+ }
+
+ m->cursor = strndup(range, colon - range);
+ }
+
+ if (!m->cursor)
+ return -ENOMEM;
+
+ m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
+ if (isempty(m->cursor)) {
+ free(m->cursor);
+ m->cursor = NULL;
+ }
+
+ return 0;
+}
+
+static int request_parse_arguments_iterator(
+ void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *value) {
+
+ RequestMeta *m = cls;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ assert(m);
+
+ if (isempty(key)) {
+ m->argument_parse_error = -EINVAL;
+ return MHD_NO;
+ }
+
+ if (streq(key, "follow")) {
+ if (isempty(value)) {
+ m->follow = true;
+ return MHD_YES;
+ }
+
+ r = parse_boolean(value);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+
+ m->follow = r;
+ 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;
+ }
+
+ if (streq(key, "boot")) {
+ if (isempty(value))
+ r = true;
+ else {
+ r = parse_boolean(value);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+ }
+
+ if (r) {
+ char match[9 + 32 + 1] = "_BOOT_ID=";
+ sd_id128_t bid;
+
+ r = sd_id128_get_boot(&bid);
+ if (r < 0) {
+ log_error("Failed to get boot ID: %s", strerror(-r));
+ return MHD_NO;
+ }
+
+ sd_id128_to_string(bid, match + 9);
+ r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+ }
+
+ return MHD_YES;
+ }
+
+ p = strjoin(key, "=", strempty(value), NULL);
+ if (!p) {
+ m->argument_parse_error = log_oom();
+ return MHD_NO;
+ }
+
+ r = sd_journal_add_match(m->journal, p, 0);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+
+ return MHD_YES;
+}
+
+static int request_parse_arguments(
+ RequestMeta *m,
+ struct MHD_Connection *connection) {
+
+ assert(m);
+ assert(connection);
+
+ m->argument_parse_error = 0;
+ MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
+
+ return m->argument_parse_error;
+}
+
+static int request_handler_entries(
+ struct MHD_Connection *connection,
+ void **connection_cls) {
+
+ struct MHD_Response *response;
+ RequestMeta *m;
+ int r;
+
+ assert(connection);
+ assert(connection_cls);
+
+ m = request_meta(connection_cls);
+ if (!m)
+ return respond_oom(connection);
+
+ r = open_journal(m);
+ if (r < 0)
+ return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+ if (request_parse_accept(m, connection) < 0)
+ return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
+
+ if (request_parse_range(m, connection) < 0)
+ return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
+
+ 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)
+ r = sd_journal_seek_head(m->journal);
+ else if (m->n_skip < 0)
+ r = sd_journal_seek_tail(m->journal);
+ if (r < 0)
+ return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
+
+ response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
+ if (!response)
+ return respond_oom(connection);
+
+ MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
+
+ r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+
+ return r;
+}
+
+static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
+ const char *eq;
+ size_t j;
+
+ eq = memchr(d, '=', l);
+ if (!eq)
+ return -EINVAL;
+
+ j = l - (eq - d + 1);
+
+ if (m == OUTPUT_JSON) {
+ fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
+ json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
+ fputs(" }\n", f);
+ } else {
+ fwrite(eq+1, 1, j, f);
+ fputc('\n', f);
+ }
+
+ return 0;
+}
+
+static ssize_t request_reader_fields(
+ void *cls,
+ uint64_t pos,
+ char *buf,
+ size_t max) {
+
+ RequestMeta *m = cls;
+ int r;
+ size_t n, k;
+
+ assert(m);
+ assert(buf);
+ assert(max > 0);
+ assert(pos >= m->delta);
+
+ pos -= m->delta;
+
+ while (pos >= m->size) {
+ off_t sz;
+ const void *d;
+ size_t l;
+
+ /* End of this field, so let's serialize the next
+ * one */
+
+ if (m->n_fields_set &&
+ m->n_fields <= 0)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+
+ r = sd_journal_enumerate_unique(m->journal, &d, &l);
+ if (r < 0) {
+ log_error("Failed to advance field index: %s", strerror(-r));
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ } else if (r == 0)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+
+ pos -= m->size;
+ m->delta += m->size;
+
+ if (m->n_fields_set)
+ m->n_fields -= 1;
+
+ if (m->tmp)
+ rewind(m->tmp);
+ else {
+ m->tmp = tmpfile();
+ if (!m->tmp) {
+ log_error("Failed to create temporary file: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+ }
+
+ r = output_field(m->tmp, m->mode, d, l);
+ if (r < 0) {
+ log_error("Failed to serialize item: %s", strerror(-r));
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ sz = ftello(m->tmp);
+ if (sz == (off_t) -1) {
+ log_error("Failed to retrieve file position: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ m->size = (uint64_t) sz;
+ }
+
+ if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
+ log_error("Failed to seek to position: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ n = m->size - pos;
+ if (n > max)
+ n = max;
+
+ errno = 0;
+ k = fread(buf, 1, n, m->tmp);
+ if (k != n) {
+ log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ return (ssize_t) k;
+}
+
+static int request_handler_fields(
+ struct MHD_Connection *connection,
+ const char *field,
+ void *connection_cls) {
+
+ struct MHD_Response *response;
+ RequestMeta *m;
+ int r;
+
+ assert(connection);
+ assert(connection_cls);
+
+ m = request_meta(connection_cls);
+ if (!m)
+ return respond_oom(connection);
+
+ r = open_journal(m);
+ if (r < 0)
+ return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+ if (request_parse_accept(m, connection) < 0)
+ return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
+
+ r = sd_journal_query_unique(m->journal, field);
+ if (r < 0)
+ return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
+
+ response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
+ if (!response)
+ return respond_oom(connection);
+
+ MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
+
+ r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+
+ return r;
+}
+
+static int request_handler_redirect(
+ struct MHD_Connection *connection,
+ const char *target) {
+
+ char *page;
+ struct MHD_Response *response;
+ int ret;
+
+ assert(connection);
+ assert(target);
+
+ if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
+ return respond_oom(connection);
+
+ response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
+ if (!response) {
+ free(page);
+ return respond_oom(connection);
+ }
+
+ MHD_add_response_header(response, "Content-Type", "text/html");
+ MHD_add_response_header(response, "Location", target);
+
+ ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
+ MHD_destroy_response(response);
+
+ return ret;
+}
+
+static int request_handler_file(
+ struct MHD_Connection *connection,
+ const char *path,
+ const char *mime_type) {
+
+ struct MHD_Response *response;
+ int ret;
+ _cleanup_close_ int fd = -1;
+ struct stat st;
+
+ assert(connection);
+ assert(path);
+ assert(mime_type);
+
+ fd = open(path, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
+
+ if (fstat(fd, &st) < 0)
+ return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
+
+ response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
+ if (!response)
+ return respond_oom(connection);
+
+ fd = -1;
+
+ MHD_add_response_header(response, "Content-Type", mime_type);
+
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+
+ return ret;
+}
+
+static int request_handler_machine(
+ struct MHD_Connection *connection,
+ void **connection_cls) {
+
+ struct MHD_Response *response;
+ RequestMeta *m;
+ int r;
+ _cleanup_free_ char* hostname = NULL, *os_name = NULL;
+ uint64_t cutoff_from, cutoff_to, usage;
+ char *json;
+ sd_id128_t mid, bid;
+ const char *v = "bare";
+
+ assert(connection);
+
+ m = request_meta(connection_cls);
+ if (!m)
+ return respond_oom(connection);
+
+ r = open_journal(m);
+ if (r < 0)
+ return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+ r = sd_id128_get_machine(&mid);
+ if (r < 0)
+ return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
+
+ r = sd_id128_get_boot(&bid);
+ if (r < 0)
+ return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
+
+ hostname = gethostname_malloc();
+ if (!hostname)
+ return respond_oom(connection);
+
+ r = sd_journal_get_usage(m->journal, &usage);
+ if (r < 0)
+ return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
+
+ r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
+ if (r < 0)
+ return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
+
+ parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
+
+ detect_virtualization(&v);
+
+ r = asprintf(&json,
+ "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
+ "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
+ "\"hostname\" : \"%s\","
+ "\"os_pretty_name\" : \"%s\","
+ "\"virtualization\" : \"%s\","
+ "\"usage\" : \"%llu\","
+ "\"cutoff_from_realtime\" : \"%llu\","
+ "\"cutoff_to_realtime\" : \"%llu\" }\n",
+ SD_ID128_FORMAT_VAL(mid),
+ SD_ID128_FORMAT_VAL(bid),
+ hostname_cleanup(hostname),
+ os_name ? os_name : "Linux",
+ v,
+ (unsigned long long) usage,
+ (unsigned long long) cutoff_from,
+ (unsigned long long) cutoff_to);
+
+ if (r < 0)
+ return respond_oom(connection);
+
+ response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
+ if (!response) {
+ free(json);
+ return respond_oom(connection);
+ }
+
+ MHD_add_response_header(response, "Content-Type", "application/json");
+ r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+
+ return r;
+}
+
+static int request_handler(
+ void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **connection_cls) {
+
+ assert(connection);
+ assert(url);
+ assert(method);
+
+ if (!streq(method, "GET"))
+ return MHD_NO;
+
+ if (streq(url, "/"))
+ return request_handler_redirect(connection, "/browse");
+
+ if (streq(url, "/entries"))
+ return request_handler_entries(connection, connection_cls);
+
+ if (startswith(url, "/fields/"))
+ return request_handler_fields(connection, url + 8, connection_cls);
+
+ if (streq(url, "/browse"))
+ return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
+
+ if (streq(url, "/machine"))
+ return request_handler_machine(connection, connection_cls);
+
+ return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
+}
+
+int main(int argc, char *argv[]) {
+ struct MHD_Daemon *d = NULL;
+ int r = EXIT_FAILURE, n;
+
+ if (argc > 1) {
+ log_error("This program does not take arguments.");
+ goto finish;
+ }
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ n = sd_listen_fds(1);
+ if (n < 0) {
+ log_error("Failed to determine passed sockets: %s", strerror(-n));
+ goto finish;
+ } else if (n > 1) {
+ log_error("Can't listen on more than one socket.");
+ goto finish;
+ } else if (n > 0) {
+ d = MHD_start_daemon(
+ MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
+ 19531,
+ NULL, NULL,
+ request_handler, NULL,
+ MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
+ MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
+ MHD_OPTION_END);
+ } else {
+ d = MHD_start_daemon(
+ MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
+ 19531,
+ NULL, NULL,
+ request_handler, NULL,
+ MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
+ MHD_OPTION_END);
+ }
+
+ if (!d) {
+ log_error("Failed to start daemon!");
+ goto finish;
+ }
+
+ pause();
+
+ r = EXIT_SUCCESS;
+
+finish:
+ if (d)
+ MHD_stop_daemon(d);
+
+ return r;
+}
diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h
new file mode 100644
index 0000000000..75a4129e5b
--- /dev/null
+++ b/src/journal/journal-internal.h
@@ -0,0 +1,127 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <systemd/sd-id128.h>
+
+#include "journal-def.h"
+#include "list.h"
+#include "hashmap.h"
+#include "journal-file.h"
+
+typedef struct Match Match;
+typedef struct Location Location;
+typedef struct Directory Directory;
+
+typedef enum MatchType {
+ MATCH_DISCRETE,
+ MATCH_OR_TERM,
+ MATCH_AND_TERM
+} MatchType;
+
+struct Match {
+ MatchType type;
+ Match *parent;
+ LIST_FIELDS(Match, matches);
+
+ /* For concrete matches */
+ char *data;
+ size_t size;
+ le64_t le_hash;
+
+ /* For terms */
+ LIST_HEAD(Match, matches);
+};
+
+typedef enum LocationType {
+ /* The first and last entries, resp. */
+ LOCATION_HEAD,
+ LOCATION_TAIL,
+
+ /* We already read the entry we currently point to, and the
+ * next one to read should probably not be this one again. */
+ LOCATION_DISCRETE,
+
+ /* We should seek to the precise location specified, and
+ * return it, as we haven't read it yet. */
+ LOCATION_SEEK
+} LocationType;
+
+struct Location {
+ LocationType type;
+
+ uint64_t seqnum;
+ sd_id128_t seqnum_id;
+ bool seqnum_set;
+
+ uint64_t realtime;
+ bool realtime_set;
+
+ uint64_t monotonic;
+ sd_id128_t boot_id;
+ bool monotonic_set;
+
+ uint64_t xor_hash;
+ bool xor_hash_set;
+};
+
+struct Directory {
+ char *path;
+ int wd;
+ bool is_root;
+};
+
+struct sd_journal {
+ int flags;
+
+ char *path;
+
+ Hashmap *files;
+ MMapCache *mmap;
+
+ Location current_location;
+
+ JournalFile *current_file;
+ uint64_t current_field;
+
+ Hashmap *directories_by_path;
+ Hashmap *directories_by_wd;
+
+ int inotify_fd;
+
+ Match *level0, *level1;
+
+ unsigned current_invalidate_counter, last_invalidate_counter;
+
+ char *unique_field;
+ JournalFile *unique_file;
+ uint64_t unique_offset;
+
+ bool on_network;
+};
+
+char *journal_make_match_string(sd_journal *j);
+void journal_print_header(sd_journal *j);
diff --git a/src/journal/journal-qrcode.c b/src/journal/journal-qrcode.c
new file mode 100644
index 0000000000..10a14e4def
--- /dev/null
+++ b/src/journal/journal-qrcode.c
@@ -0,0 +1,138 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <qrencode.h>
+
+#include "journal-qrcode.h"
+
+#define WHITE_ON_BLACK "\033[40;37;1m"
+#define NORMAL "\033[0m"
+
+static void print_border(FILE *output, unsigned width) {
+ unsigned x, y;
+
+ /* Four rows of border */
+ for (y = 0; y < 4; y += 2) {
+ fputs(WHITE_ON_BLACK, output);
+
+ for (x = 0; x < 4 + width + 4; x++)
+ fputs("\342\226\210", output);
+
+ fputs(NORMAL "\n", output);
+ }
+}
+
+int print_qr_code(
+ FILE *output,
+ const void *seed,
+ size_t seed_size,
+ uint64_t start,
+ uint64_t interval,
+ const char *hn,
+ sd_id128_t machine) {
+
+ FILE *f;
+ char *url = NULL;
+ size_t url_size = 0, i;
+ QRcode* qr;
+ unsigned x, y;
+
+ assert(seed);
+ assert(seed_size > 0);
+
+ f = open_memstream(&url, &url_size);
+ if (!f)
+ return -ENOMEM;
+
+ fputs("fss://", f);
+
+ for (i = 0; i < seed_size; i++) {
+ if (i > 0 && i % 3 == 0)
+ fputc('-', f);
+ fprintf(f, "%02x", ((uint8_t*) seed)[i]);
+ }
+
+ fprintf(f, "/%llx-%llx?machine=" SD_ID128_FORMAT_STR,
+ (unsigned long long) start,
+ (unsigned long long) interval,
+ SD_ID128_FORMAT_VAL(machine));
+
+ if (hn)
+ fprintf(f, ";hostname=%s", hn);
+
+ if (ferror(f)) {
+ fclose(f);
+ free(url);
+ return -ENOMEM;
+ }
+
+ fclose(f);
+
+ qr = QRcode_encodeString(url, 0, QR_ECLEVEL_L, QR_MODE_8, 1);
+ free(url);
+
+ if (!qr)
+ return -ENOMEM;
+
+ print_border(output, qr->width);
+
+ for (y = 0; y < (unsigned) qr->width; y += 2) {
+ const uint8_t *row1, *row2;
+
+ row1 = qr->data + qr->width * y;
+ row2 = row1 + qr->width;
+
+ fputs(WHITE_ON_BLACK, output);
+ for (x = 0; x < 4; x++)
+ fputs("\342\226\210", output);
+
+ for (x = 0; x < (unsigned) qr->width; x ++) {
+ bool a, b;
+
+ a = row1[x] & 1;
+ b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false;
+
+ if (a && b)
+ fputc(' ', output);
+ else if (a)
+ fputs("\342\226\204", output);
+ else if (b)
+ fputs("\342\226\200", output);
+ else
+ fputs("\342\226\210", output);
+ }
+
+ for (x = 0; x < 4; x++)
+ fputs("\342\226\210", output);
+ fputs(NORMAL "\n", output);
+ }
+
+ print_border(output, qr->width);
+
+ QRcode_free(qr);
+ return 0;
+}
diff --git a/src/journal/journal-qrcode.h b/src/journal/journal-qrcode.h
new file mode 100644
index 0000000000..da6244c160
--- /dev/null
+++ b/src/journal/journal-qrcode.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#include <systemd/sd-id128.h>
+
+int print_qr_code(FILE *f, const void *seed, size_t seed_size, uint64_t start, uint64_t interval, const char *hn, sd_id128_t machine);
diff --git a/src/journal/journal-send.c b/src/journal/journal-send.c
new file mode 100644
index 0000000000..8589d94479
--- /dev/null
+++ b/src/journal/journal-send.c
@@ -0,0 +1,608 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <printf.h>
+
+#define SD_JOURNAL_SUPPRESS_LOCATION
+
+#include "sd-journal.h"
+#include "util.h"
+#include "socket-util.h"
+
+#define SNDBUF_SIZE (8*1024*1024)
+
+/* We open a single fd, and we'll share it with the current process,
+ * all its threads, and all its subprocesses. This means we need to
+ * initialize it atomically, and need to operate on it atomically
+ * never assuming we are the only user */
+
+static int journal_fd(void) {
+ int fd;
+ static int fd_plus_one = 0;
+
+retry:
+ if (fd_plus_one > 0)
+ return fd_plus_one - 1;
+
+ fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return -errno;
+
+ fd_inc_sndbuf(fd, SNDBUF_SIZE);
+
+ if (!__sync_bool_compare_and_swap(&fd_plus_one, 0, fd+1)) {
+ close_nointr_nofail(fd);
+ goto retry;
+ }
+
+ return fd;
+}
+
+_public_ int sd_journal_print(int priority, const char *format, ...) {
+ int r;
+ va_list ap;
+
+ va_start(ap, format);
+ r = sd_journal_printv(priority, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_journal_printv(int priority, const char *format, va_list ap) {
+
+ /* FIXME: Instead of limiting things to LINE_MAX we could do a
+ C99 variable-length array on the stack here in a loop. */
+
+ char buffer[8 + LINE_MAX], p[11]; struct iovec iov[2];
+
+ if (priority < 0 || priority > 7)
+ return -EINVAL;
+
+ if (!format)
+ return -EINVAL;
+
+ snprintf(p, sizeof(p), "PRIORITY=%i", priority & LOG_PRIMASK);
+ char_array_0(p);
+
+ memcpy(buffer, "MESSAGE=", 8);
+ vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap);
+ char_array_0(buffer);
+
+ zero(iov);
+ IOVEC_SET_STRING(iov[0], buffer);
+ IOVEC_SET_STRING(iov[1], p);
+
+ return sd_journal_sendv(iov, 2);
+}
+
+static int fill_iovec_sprintf(const char *format, va_list ap, int extra, struct iovec **_iov) {
+ int r, n = 0, i = 0, j;
+ struct iovec *iov = NULL;
+ int saved_errno;
+
+ assert(_iov);
+ saved_errno = errno;
+
+ if (extra > 0) {
+ n = MAX(extra * 2, extra + 4);
+ iov = malloc0(n * sizeof(struct iovec));
+ if (!iov) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ i = extra;
+ }
+
+ while (format) {
+ struct iovec *c;
+ char *buffer;
+ va_list aq;
+
+ if (i >= n) {
+ n = MAX(i*2, 4);
+ c = realloc(iov, n * sizeof(struct iovec));
+ if (!c) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ iov = c;
+ }
+
+ va_copy(aq, ap);
+ if (vasprintf(&buffer, format, aq) < 0) {
+ va_end(aq);
+ r = -ENOMEM;
+ goto fail;
+ }
+ va_end(aq);
+
+ VA_FORMAT_ADVANCE(format, ap);
+
+ IOVEC_SET_STRING(iov[i++], buffer);
+
+ format = va_arg(ap, char *);
+ }
+
+ *_iov = iov;
+
+ errno = saved_errno;
+ return i;
+
+fail:
+ for (j = 0; j < i; j++)
+ free(iov[j].iov_base);
+
+ free(iov);
+
+ errno = saved_errno;
+ return r;
+}
+
+_public_ int sd_journal_send(const char *format, ...) {
+ int r, i, j;
+ va_list ap;
+ struct iovec *iov = NULL;
+
+ va_start(ap, format);
+ i = fill_iovec_sprintf(format, ap, 0, &iov);
+ va_end(ap);
+
+ if (_unlikely_(i < 0)) {
+ r = i;
+ goto finish;
+ }
+
+ r = sd_journal_sendv(iov, i);
+
+finish:
+ for (j = 0; j < i; j++)
+ free(iov[j].iov_base);
+
+ free(iov);
+
+ return r;
+}
+
+_public_ int sd_journal_sendv(const struct iovec *iov, int n) {
+ int fd, buffer_fd;
+ struct iovec *w;
+ uint64_t *l;
+ int r, i, j = 0;
+ struct msghdr mh;
+ struct sockaddr_un sa;
+ ssize_t k;
+ int saved_errno;
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(int))];
+ } control;
+ struct cmsghdr *cmsg;
+ /* We use /dev/shm instead of /tmp here, since we want this to
+ * be a tmpfs, and one that is available from early boot on
+ * and where unprivileged users can create files. */
+ char path[] = "/dev/shm/journal.XXXXXX";
+
+ if (_unlikely_(!iov))
+ return -EINVAL;
+
+ if (_unlikely_(n <= 0))
+ return -EINVAL;
+
+ saved_errno = errno;
+
+ w = alloca(sizeof(struct iovec) * n * 5);
+ l = alloca(sizeof(uint64_t) * n);
+
+ for (i = 0; i < n; i++) {
+ char *c, *nl;
+
+ if (_unlikely_(!iov[i].iov_base || iov[i].iov_len <= 1)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ c = memchr(iov[i].iov_base, '=', iov[i].iov_len);
+ if (_unlikely_(!c || c == iov[i].iov_base)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ nl = memchr(iov[i].iov_base, '\n', iov[i].iov_len);
+ if (nl) {
+ if (_unlikely_(nl < c)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ /* Already includes a newline? Bummer, then
+ * let's write the variable name, then a
+ * newline, then the size (64bit LE), followed
+ * by the data and a final newline */
+
+ w[j].iov_base = iov[i].iov_base;
+ w[j].iov_len = c - (char*) iov[i].iov_base;
+ j++;
+
+ IOVEC_SET_STRING(w[j++], "\n");
+
+ l[i] = htole64(iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1);
+ w[j].iov_base = &l[i];
+ w[j].iov_len = sizeof(uint64_t);
+ j++;
+
+ w[j].iov_base = c + 1;
+ w[j].iov_len = iov[i].iov_len - (c - (char*) iov[i].iov_base) - 1;
+ j++;
+
+ } else
+ /* Nothing special? Then just add the line and
+ * append a newline */
+ w[j++] = iov[i];
+
+ IOVEC_SET_STRING(w[j++], "\n");
+ }
+
+ fd = journal_fd();
+ if (_unlikely_(fd < 0)) {
+ r = fd;
+ goto finish;
+ }
+
+ zero(sa);
+ sa.sun_family = AF_UNIX;
+ strncpy(sa.sun_path, "/run/systemd/journal/socket", sizeof(sa.sun_path));
+
+ zero(mh);
+ mh.msg_name = &sa;
+ mh.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path);
+ mh.msg_iov = w;
+ mh.msg_iovlen = j;
+
+ k = sendmsg(fd, &mh, MSG_NOSIGNAL);
+ if (k >= 0) {
+ r = 0;
+ goto finish;
+ }
+
+ if (errno != EMSGSIZE && errno != ENOBUFS) {
+ r = -errno;
+ goto finish;
+ }
+
+ /* Message doesn't fit... Let's dump the data in a temporary
+ * file and just pass a file descriptor of it to the other
+ * side */
+
+ buffer_fd = mkostemp(path, O_CLOEXEC|O_RDWR);
+ if (buffer_fd < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (unlink(path) < 0) {
+ close_nointr_nofail(buffer_fd);
+ r = -errno;
+ goto finish;
+ }
+
+ n = writev(buffer_fd, w, j);
+ if (n < 0) {
+ close_nointr_nofail(buffer_fd);
+ r = -errno;
+ goto finish;
+ }
+
+ mh.msg_iov = NULL;
+ mh.msg_iovlen = 0;
+
+ zero(control);
+ mh.msg_control = &control;
+ mh.msg_controllen = sizeof(control);
+
+ cmsg = CMSG_FIRSTHDR(&mh);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cmsg), &buffer_fd, sizeof(int));
+
+ mh.msg_controllen = cmsg->cmsg_len;
+
+ k = sendmsg(fd, &mh, MSG_NOSIGNAL);
+ close_nointr_nofail(buffer_fd);
+
+ if (k < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ errno = saved_errno;
+
+ return r;
+}
+
+static int fill_iovec_perror_and_send(const char *message, int skip, struct iovec iov[]) {
+ size_t n, k, r;
+ int saved_errno;
+
+ saved_errno = errno;
+
+ k = isempty(message) ? 0 : strlen(message) + 2;
+ n = 8 + k + 256 + 1;
+
+ for (;;) {
+ char buffer[n];
+ char* j;
+
+ errno = 0;
+ j = strerror_r(saved_errno, buffer + 8 + k, n - 8 - k);
+ if (errno == 0) {
+ char error[6 + 10 + 1]; /* for a 32bit value */
+
+ if (j != buffer + 8 + k)
+ memmove(buffer + 8 + k, j, strlen(j)+1);
+
+ memcpy(buffer, "MESSAGE=", 8);
+
+ if (k > 0) {
+ memcpy(buffer + 8, message, k - 2);
+ memcpy(buffer + 8 + k - 2, ": ", 2);
+ }
+
+ snprintf(error, sizeof(error), "ERRNO=%u", saved_errno);
+ char_array_0(error);
+
+ IOVEC_SET_STRING(iov[skip+0], "PRIORITY=3");
+ IOVEC_SET_STRING(iov[skip+1], buffer);
+ IOVEC_SET_STRING(iov[skip+2], error);
+
+ r = sd_journal_sendv(iov, skip + 3);
+
+ errno = saved_errno;
+ return r;
+ }
+
+ if (errno != ERANGE) {
+ r = -errno;
+ errno = saved_errno;
+ return r;
+ }
+
+ n *= 2;
+ }
+}
+
+_public_ int sd_journal_perror(const char *message) {
+ struct iovec iovec[3];
+
+ return fill_iovec_perror_and_send(message, 0, iovec);
+}
+
+_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) {
+ union sockaddr_union sa;
+ int fd;
+ char *header;
+ size_t l;
+ ssize_t r;
+
+ if (priority < 0 || priority > 7)
+ return -EINVAL;
+
+ fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return -errno;
+
+ zero(sa);
+ sa.un.sun_family = AF_UNIX;
+ strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path));
+
+ r = connect(fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
+ if (r < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ if (shutdown(fd, SHUT_RD) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ fd_inc_sndbuf(fd, SNDBUF_SIZE);
+
+ if (!identifier)
+ identifier = "";
+
+ l = strlen(identifier);
+ header = alloca(l + 1 + 1 + 2 + 2 + 2 + 2 + 2);
+
+ memcpy(header, identifier, l);
+ header[l++] = '\n';
+ header[l++] = '\n'; /* unit id */
+ header[l++] = '0' + priority;
+ header[l++] = '\n';
+ header[l++] = '0' + !!level_prefix;
+ header[l++] = '\n';
+ header[l++] = '0';
+ header[l++] = '\n';
+ header[l++] = '0';
+ header[l++] = '\n';
+ header[l++] = '0';
+ header[l++] = '\n';
+
+ r = loop_write(fd, header, l, false);
+ if (r < 0) {
+ close_nointr_nofail(fd);
+ return (int) r;
+ }
+
+ if ((size_t) r != l) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ return fd;
+}
+
+_public_ int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) {
+ int r;
+ va_list ap;
+
+ va_start(ap, format);
+ r = sd_journal_printv_with_location(priority, file, line, func, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+_public_ int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) {
+ char buffer[8 + LINE_MAX], p[11];
+ struct iovec iov[5];
+ char *f;
+ size_t fl;
+
+ if (priority < 0 || priority > 7)
+ return -EINVAL;
+
+ if (_unlikely_(!format))
+ return -EINVAL;
+
+ snprintf(p, sizeof(p), "PRIORITY=%i", priority & LOG_PRIMASK);
+ char_array_0(p);
+
+ memcpy(buffer, "MESSAGE=", 8);
+ vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap);
+ char_array_0(buffer);
+
+ /* func is initialized from __func__ which is not a macro, but
+ * a static const char[], hence cannot easily be prefixed with
+ * CODE_FUNC=, hence let's do it manually here. */
+ fl = strlen(func) + 1;
+ f = alloca(fl + 10);
+ memcpy(f, "CODE_FUNC=", 10);
+ memcpy(f + 10, func, fl);
+
+ zero(iov);
+ IOVEC_SET_STRING(iov[0], buffer);
+ IOVEC_SET_STRING(iov[1], p);
+ IOVEC_SET_STRING(iov[2], file);
+ IOVEC_SET_STRING(iov[3], line);
+ IOVEC_SET_STRING(iov[4], f);
+
+ return sd_journal_sendv(iov, ELEMENTSOF(iov));
+}
+
+_public_ int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) {
+ int r, i, j;
+ va_list ap;
+ struct iovec *iov = NULL;
+ char *f;
+ size_t fl;
+
+ va_start(ap, format);
+ i = fill_iovec_sprintf(format, ap, 3, &iov);
+ va_end(ap);
+
+ if (_unlikely_(i < 0)) {
+ r = i;
+ goto finish;
+ }
+
+ fl = strlen(func) + 1;
+ f = alloca(fl + 10);
+ memcpy(f, "CODE_FUNC=", 10);
+ memcpy(f + 10, func, fl);
+
+ IOVEC_SET_STRING(iov[0], file);
+ IOVEC_SET_STRING(iov[1], line);
+ IOVEC_SET_STRING(iov[2], f);
+
+ r = sd_journal_sendv(iov, i);
+
+finish:
+ for (j = 3; j < i; j++)
+ free(iov[j].iov_base);
+
+ free(iov);
+
+ return r;
+}
+
+_public_ int sd_journal_sendv_with_location(
+ const char *file, const char *line,
+ const char *func,
+ const struct iovec *iov, int n) {
+
+ struct iovec *niov;
+ char *f;
+ size_t fl;
+
+ if (_unlikely_(!iov))
+ return -EINVAL;
+
+ if (_unlikely_(n <= 0))
+ return -EINVAL;
+
+ niov = alloca(sizeof(struct iovec) * (n + 3));
+ memcpy(niov, iov, sizeof(struct iovec) * n);
+
+ fl = strlen(func) + 1;
+ f = alloca(fl + 10);
+ memcpy(f, "CODE_FUNC=", 10);
+ memcpy(f + 10, func, fl);
+
+ IOVEC_SET_STRING(niov[n++], file);
+ IOVEC_SET_STRING(niov[n++], line);
+ IOVEC_SET_STRING(niov[n++], f);
+
+ return sd_journal_sendv(niov, n);
+}
+
+_public_ int sd_journal_perror_with_location(
+ const char *file, const char *line,
+ const char *func,
+ const char *message) {
+
+ struct iovec iov[6];
+ size_t fl;
+ char *f;
+
+ fl = strlen(func) + 1;
+ f = alloca(fl + 10);
+ memcpy(f, "CODE_FUNC=", 10);
+ memcpy(f + 10, func, fl);
+
+ IOVEC_SET_STRING(iov[0], file);
+ IOVEC_SET_STRING(iov[1], line);
+ IOVEC_SET_STRING(iov[2], f);
+
+ return fill_iovec_perror_and_send(message, 3, iov);
+}
diff --git a/src/journal/journal-vacuum.c b/src/journal/journal-vacuum.c
new file mode 100644
index 0000000000..731f6c770f
--- /dev/null
+++ b/src/journal/journal-vacuum.c
@@ -0,0 +1,318 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <unistd.h>
+
+#ifdef HAVE_XATTR
+#include <attr/xattr.h>
+#endif
+
+#include "journal-def.h"
+#include "journal-file.h"
+#include "journal-vacuum.h"
+#include "sd-id128.h"
+#include "util.h"
+
+struct vacuum_info {
+ off_t usage;
+ char *filename;
+
+ uint64_t realtime;
+ sd_id128_t seqnum_id;
+ uint64_t seqnum;
+
+ bool have_seqnum;
+};
+
+static int vacuum_compare(const void *_a, const void *_b) {
+ const struct vacuum_info *a, *b;
+
+ a = _a;
+ b = _b;
+
+ if (a->have_seqnum && b->have_seqnum &&
+ sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
+ if (a->seqnum < b->seqnum)
+ return -1;
+ else if (a->seqnum > b->seqnum)
+ return 1;
+ else
+ return 0;
+ }
+
+ if (a->realtime < b->realtime)
+ return -1;
+ else if (a->realtime > b->realtime)
+ return 1;
+ else if (a->have_seqnum && b->have_seqnum)
+ return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
+ else
+ return strcmp(a->filename, b->filename);
+}
+
+static void patch_realtime(
+ const char *dir,
+ const char *fn,
+ const struct stat *st,
+ unsigned long long *realtime) {
+
+ usec_t x;
+
+#ifdef HAVE_XATTR
+ uint64_t crtime;
+ _cleanup_free_ const char *path = NULL;
+#endif
+
+ /* The timestamp was determined by the file name, but let's
+ * see if the file might actually be older than the file name
+ * suggested... */
+
+ assert(dir);
+ assert(fn);
+ assert(st);
+ assert(realtime);
+
+ x = timespec_load(&st->st_ctim);
+ if (x > 0 && x != (usec_t) -1 && x < *realtime)
+ *realtime = x;
+
+ x = timespec_load(&st->st_atim);
+ if (x > 0 && x != (usec_t) -1 && x < *realtime)
+ *realtime = x;
+
+ x = timespec_load(&st->st_mtim);
+ if (x > 0 && x != (usec_t) -1 && x < *realtime)
+ *realtime = x;
+
+#ifdef HAVE_XATTR
+ /* Let's read the original creation time, if possible. Ideally
+ * we'd just query the creation time the FS might provide, but
+ * unfortunately there's currently no sane API to query
+ * it. Hence let's implement this manually... */
+
+ /* Unfortunately there is is not fgetxattrat(), so we need to
+ * go via path here. :-( */
+
+ path = strjoin(dir, "/", fn, NULL);
+ if (!path)
+ return;
+
+ if (getxattr(path, "user.crtime_usec", &crtime, sizeof(crtime)) == sizeof(crtime)) {
+ crtime = le64toh(crtime);
+
+ if (crtime > 0 && crtime != (uint64_t) -1 && crtime < *realtime)
+ *realtime = crtime;
+ }
+#endif
+}
+
+int journal_directory_vacuum(
+ const char *directory,
+ uint64_t max_use,
+ uint64_t min_free,
+ usec_t max_retention_usec,
+ usec_t *oldest_usec) {
+
+ DIR *d;
+ int r = 0;
+ struct vacuum_info *list = NULL;
+ unsigned n_list = 0, n_allocated = 0, i;
+ uint64_t sum = 0;
+ usec_t retention_limit = 0;
+
+ assert(directory);
+
+ if (max_use <= 0 && min_free <= 0 && max_retention_usec <= 0)
+ return 0;
+
+ if (max_retention_usec > 0) {
+ retention_limit = now(CLOCK_REALTIME);
+ if (retention_limit > max_retention_usec)
+ retention_limit -= max_retention_usec;
+ else
+ max_retention_usec = retention_limit = 0;
+ }
+
+ d = opendir(directory);
+ if (!d)
+ return -errno;
+
+ for (;;) {
+ int k;
+ struct dirent *de;
+ union dirent_storage buf;
+ size_t q;
+ struct stat st;
+ char *p;
+ unsigned long long seqnum = 0, realtime;
+ sd_id128_t seqnum_id;
+ bool have_seqnum;
+
+ k = readdir_r(d, &buf.de, &de);
+ if (k != 0) {
+ r = -k;
+ goto finish;
+ }
+
+ if (!de)
+ break;
+
+ if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
+ continue;
+
+ if (!S_ISREG(st.st_mode))
+ continue;
+
+ q = strlen(de->d_name);
+
+ if (endswith(de->d_name, ".journal")) {
+
+ /* Vacuum archived files */
+
+ if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
+ continue;
+
+ if (de->d_name[q-8-16-1] != '-' ||
+ de->d_name[q-8-16-1-16-1] != '-' ||
+ de->d_name[q-8-16-1-16-1-32-1] != '@')
+ continue;
+
+ p = strdup(de->d_name);
+ if (!p) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ de->d_name[q-8-16-1-16-1] = 0;
+ if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
+ free(p);
+ continue;
+ }
+
+ if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
+ free(p);
+ continue;
+ }
+
+ have_seqnum = true;
+
+ } else if (endswith(de->d_name, ".journal~")) {
+ unsigned long long tmp;
+
+ /* Vacuum corrupted files */
+
+ if (q < 1 + 16 + 1 + 16 + 8 + 1)
+ continue;
+
+ if (de->d_name[q-1-8-16-1] != '-' ||
+ de->d_name[q-1-8-16-1-16-1] != '@')
+ continue;
+
+ p = strdup(de->d_name);
+ if (!p) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
+ free(p);
+ continue;
+ }
+
+ have_seqnum = false;
+ } else
+ /* We do not vacuum active files or unknown files! */
+ continue;
+
+ patch_realtime(directory, de->d_name, &st, &realtime);
+
+ if (n_list >= n_allocated) {
+ struct vacuum_info *j;
+
+ n_allocated = MAX(n_allocated * 2U, 8U);
+ j = realloc(list, n_allocated * sizeof(struct vacuum_info));
+ if (!j) {
+ free(p);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ list = j;
+ }
+
+ list[n_list].filename = p;
+ list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
+ list[n_list].seqnum = seqnum;
+ list[n_list].realtime = realtime;
+ list[n_list].seqnum_id = seqnum_id;
+ list[n_list].have_seqnum = have_seqnum;
+
+ sum += list[n_list].usage;
+
+ n_list ++;
+ }
+
+ if (n_list > 0)
+ qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
+
+ for (i = 0; i < n_list; i++) {
+ struct statvfs ss;
+
+ if (fstatvfs(dirfd(d), &ss) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if ((max_retention_usec <= 0 || list[i].realtime >= retention_limit) &&
+ (max_use <= 0 || sum <= max_use) &&
+ (min_free <= 0 || (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free))
+ break;
+
+ if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
+ log_debug("Deleted archived journal %s/%s.", directory, list[i].filename);
+
+ if ((uint64_t) list[i].usage > sum)
+ sum -= list[i].usage;
+ else
+ sum = 0;
+
+ } else if (errno != ENOENT)
+ log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
+ }
+
+ if (oldest_usec && i < n_list && (*oldest_usec == 0 || list[i].realtime < *oldest_usec))
+ *oldest_usec = list[i].realtime;
+
+finish:
+ for (i = 0; i < n_list; i++)
+ free(list[i].filename);
+
+ free(list);
+
+ if (d)
+ closedir(d);
+
+ return r;
+}
diff --git a/src/journal/journal-vacuum.h b/src/journal/journal-vacuum.h
new file mode 100644
index 0000000000..f5e3e5291f
--- /dev/null
+++ b/src/journal/journal-vacuum.h
@@ -0,0 +1,26 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free, usec_t max_retention_usec, usec_t *oldest_usec);
diff --git a/src/journal/journal-verify.c b/src/journal/journal-verify.c
new file mode 100644
index 0000000000..1a67d5a04b
--- /dev/null
+++ b/src/journal/journal-verify.c
@@ -0,0 +1,1163 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <stddef.h>
+
+#include "util.h"
+#include "macro.h"
+#include "journal-def.h"
+#include "journal-file.h"
+#include "journal-authenticate.h"
+#include "journal-verify.h"
+#include "lookup3.h"
+#include "compress.h"
+#include "fsprg.h"
+
+static int journal_file_object_verify(JournalFile *f, Object *o) {
+ uint64_t i;
+
+ assert(f);
+ assert(o);
+
+ /* This does various superficial tests about the length an
+ * possible field values. It does not follow any references to
+ * other objects. */
+
+ if ((o->object.flags & OBJECT_COMPRESSED) &&
+ o->object.type != OBJECT_DATA)
+ return -EBADMSG;
+
+ switch (o->object.type) {
+
+ case OBJECT_DATA: {
+ uint64_t h1, h2;
+
+ if (le64toh(o->data.entry_offset) <= 0 ||
+ le64toh(o->data.n_entries) <= 0)
+ return -EBADMSG;
+
+ if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0)
+ return -EBADMSG;
+
+ h1 = le64toh(o->data.hash);
+
+ if (o->object.flags & OBJECT_COMPRESSED) {
+#ifdef HAVE_XZ
+ void *b = NULL;
+ uint64_t alloc = 0, b_size;
+
+ if (!uncompress_blob(o->data.payload,
+ le64toh(o->object.size) - offsetof(Object, data.payload),
+ &b, &alloc, &b_size))
+ return -EBADMSG;
+
+ h2 = hash64(b, b_size);
+ free(b);
+#else
+ return -EPROTONOSUPPORT;
+#endif
+ } else
+ h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
+
+ if (h1 != h2)
+ return -EBADMSG;
+
+ if (!VALID64(o->data.next_hash_offset) ||
+ !VALID64(o->data.next_field_offset) ||
+ !VALID64(o->data.entry_offset) ||
+ !VALID64(o->data.entry_array_offset))
+ return -EBADMSG;
+
+ break;
+ }
+
+ case OBJECT_FIELD:
+ if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0)
+ return -EBADMSG;
+
+ if (!VALID64(o->field.next_hash_offset) ||
+ !VALID64(o->field.head_data_offset))
+ return -EBADMSG;
+ break;
+
+ case OBJECT_ENTRY:
+ if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0)
+ return -EBADMSG;
+
+ if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0)
+ return -EBADMSG;
+
+ if (le64toh(o->entry.seqnum) <= 0 ||
+ !VALID_REALTIME(le64toh(o->entry.realtime)) ||
+ !VALID_MONOTONIC(le64toh(o->entry.monotonic)))
+ return -EBADMSG;
+
+ for (i = 0; i < journal_file_entry_n_items(o); i++) {
+ if (o->entry.items[i].object_offset == 0 ||
+ !VALID64(o->entry.items[i].object_offset))
+ return -EBADMSG;
+ }
+
+ break;
+
+ case OBJECT_DATA_HASH_TABLE:
+ case OBJECT_FIELD_HASH_TABLE:
+ if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0)
+ return -EBADMSG;
+
+ if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0)
+ return -EBADMSG;
+
+ for (i = 0; i < journal_file_hash_table_n_items(o); i++) {
+ if (o->hash_table.items[i].head_hash_offset != 0 &&
+ !VALID64(le64toh(o->hash_table.items[i].head_hash_offset)))
+ return -EBADMSG;
+ if (o->hash_table.items[i].tail_hash_offset != 0 &&
+ !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset)))
+ return -EBADMSG;
+
+ if ((o->hash_table.items[i].head_hash_offset != 0) !=
+ (o->hash_table.items[i].tail_hash_offset != 0))
+ return -EBADMSG;
+ }
+
+ break;
+
+ case OBJECT_ENTRY_ARRAY:
+ if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0)
+ return -EBADMSG;
+
+ if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0)
+ return -EBADMSG;
+
+ if (!VALID64(o->entry_array.next_entry_array_offset))
+ return -EBADMSG;
+
+ for (i = 0; i < journal_file_entry_array_n_items(o); i++)
+ if (o->entry_array.items[i] != 0 &&
+ !VALID64(o->entry_array.items[i]))
+ return -EBADMSG;
+
+ break;
+
+ case OBJECT_TAG:
+ if (le64toh(o->object.size) != sizeof(TagObject))
+ return -EBADMSG;
+
+ if (!VALID_EPOCH(o->tag.epoch))
+ return -EBADMSG;
+
+ break;
+ }
+
+ return 0;
+}
+
+static void draw_progress(uint64_t p, usec_t *last_usec) {
+ unsigned n, i, j, k;
+ usec_t z, x;
+
+ if (!on_tty())
+ return;
+
+ z = now(CLOCK_MONOTONIC);
+ x = *last_usec;
+
+ if (x != 0 && x + 40 * USEC_PER_MSEC > z)
+ return;
+
+ *last_usec = z;
+
+ n = (3 * columns()) / 4;
+ j = (n * (unsigned) p) / 65535ULL;
+ k = n - j;
+
+ fputs("\r\x1B[?25l" ANSI_HIGHLIGHT_GREEN_ON, stdout);
+
+ for (i = 0; i < j; i++)
+ fputs("\xe2\x96\x88", stdout);
+
+ fputs(ANSI_HIGHLIGHT_OFF, stdout);
+
+ for (i = 0; i < k; i++)
+ fputs("\xe2\x96\x91", stdout);
+
+ printf(" %3lu%%", 100LU * (unsigned long) p / 65535LU);
+
+ fputs("\r\x1B[?25h", stdout);
+ fflush(stdout);
+}
+
+static void flush_progress(void) {
+ unsigned n, i;
+
+ if (!on_tty())
+ return;
+
+ n = (3 * columns()) / 4;
+
+ putchar('\r');
+
+ for (i = 0; i < n + 5; i++)
+ putchar(' ');
+
+ putchar('\r');
+ fflush(stdout);
+}
+
+static int write_uint64(int fd, uint64_t p) {
+ ssize_t k;
+
+ k = write(fd, &p, sizeof(p));
+ if (k < 0)
+ return -errno;
+ if (k != sizeof(p))
+ return -EIO;
+
+ return 0;
+}
+
+static int contains_uint64(MMapCache *m, int fd, uint64_t n, uint64_t p) {
+ uint64_t a, b;
+ int r;
+
+ assert(m);
+ assert(fd >= 0);
+
+ /* Bisection ... */
+
+ a = 0; b = n;
+ while (a < b) {
+ uint64_t c, *z;
+
+ c = (a + b) / 2;
+
+ r = mmap_cache_get(m, fd, PROT_READ|PROT_WRITE, 0, false, c * sizeof(uint64_t), sizeof(uint64_t), NULL, (void **) &z);
+ if (r < 0)
+ return r;
+
+ if (*z == p)
+ return 1;
+
+ if (a + 1 >= b)
+ return 0;
+
+ if (p < *z)
+ b = c;
+ else
+ a = c;
+ }
+
+ return 0;
+}
+
+static int entry_points_to_data(
+ JournalFile *f,
+ int entry_fd,
+ uint64_t n_entries,
+ uint64_t entry_p,
+ uint64_t data_p) {
+
+ int r;
+ uint64_t i, n, a;
+ Object *o;
+ bool found = false;
+
+ assert(f);
+ assert(entry_fd >= 0);
+
+ if (!contains_uint64(f->mmap, entry_fd, n_entries, entry_p)) {
+ log_error("Data object references invalid entry at %llu", (unsigned long long) data_p);
+ return -EBADMSG;
+ }
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, entry_p, &o);
+ if (r < 0)
+ return r;
+
+ n = journal_file_entry_n_items(o);
+ for (i = 0; i < n; i++)
+ if (le64toh(o->entry.items[i].object_offset) == data_p) {
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ log_error("Data object not referenced by linked entry at %llu", (unsigned long long) data_p);
+ return -EBADMSG;
+ }
+
+ /* Check if this entry is also in main entry array. Since the
+ * main entry array has already been verified we can rely on
+ * its consistency.*/
+
+ i = 0;
+ n = le64toh(f->header->n_entries);
+ a = le64toh(f->header->entry_array_offset);
+
+ while (i < n) {
+ uint64_t m, u;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
+ if (r < 0)
+ return r;
+
+ m = journal_file_entry_array_n_items(o);
+ u = MIN(n - i, m);
+
+ if (entry_p <= le64toh(o->entry_array.items[u-1])) {
+ uint64_t x, y, z;
+
+ x = 0;
+ y = u;
+
+ while (x < y) {
+ z = (x + y) / 2;
+
+ if (le64toh(o->entry_array.items[z]) == entry_p)
+ return 0;
+
+ if (x + 1 >= y)
+ break;
+
+ if (entry_p < le64toh(o->entry_array.items[z]))
+ y = z;
+ else
+ x = z;
+ }
+
+ log_error("Entry object doesn't exist in main entry array at %llu", (unsigned long long) entry_p);
+ return -EBADMSG;
+ }
+
+ i += u;
+ a = le64toh(o->entry_array.next_entry_array_offset);
+ }
+
+ return 0;
+}
+
+static int verify_data(
+ JournalFile *f,
+ Object *o, uint64_t p,
+ int entry_fd, uint64_t n_entries,
+ int entry_array_fd, uint64_t n_entry_arrays) {
+
+ uint64_t i, n, a, last, q;
+ int r;
+
+ assert(f);
+ assert(o);
+ assert(entry_fd >= 0);
+ assert(entry_array_fd >= 0);
+
+ n = le64toh(o->data.n_entries);
+ a = le64toh(o->data.entry_array_offset);
+
+ /* We already checked this earlier */
+ assert(n > 0);
+
+ last = q = le64toh(o->data.entry_offset);
+ r = entry_points_to_data(f, entry_fd, n_entries, q, p);
+ if (r < 0)
+ return r;
+
+ i = 1;
+ while (i < n) {
+ uint64_t next, m, j;
+
+ if (a == 0) {
+ log_error("Array chain too short at %llu", (unsigned long long) p);
+ return -EBADMSG;
+ }
+
+ if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
+ log_error("Invalid array at %llu", (unsigned long long) p);
+ return -EBADMSG;
+ }
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
+ if (r < 0)
+ return r;
+
+ next = le64toh(o->entry_array.next_entry_array_offset);
+ if (next != 0 && next <= a) {
+ log_error("Array chain has cycle at %llu", (unsigned long long) p);
+ return -EBADMSG;
+ }
+
+ m = journal_file_entry_array_n_items(o);
+ for (j = 0; i < n && j < m; i++, j++) {
+
+ q = le64toh(o->entry_array.items[j]);
+ if (q <= last) {
+ log_error("Data object's entry array not sorted at %llu", (unsigned long long) p);
+ return -EBADMSG;
+ }
+ last = q;
+
+ r = entry_points_to_data(f, entry_fd, n_entries, q, p);
+ if (r < 0)
+ return r;
+
+ /* Pointer might have moved, reposition */
+ r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
+ if (r < 0)
+ return r;
+ }
+
+ a = next;
+ }
+
+ return 0;
+}
+
+static int verify_hash_table(
+ JournalFile *f,
+ int data_fd, uint64_t n_data,
+ int entry_fd, uint64_t n_entries,
+ int entry_array_fd, uint64_t n_entry_arrays,
+ usec_t *last_usec,
+ bool show_progress) {
+
+ uint64_t i, n;
+ int r;
+
+ assert(f);
+ assert(data_fd >= 0);
+ assert(entry_fd >= 0);
+ assert(entry_array_fd >= 0);
+ assert(last_usec);
+
+ n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
+ for (i = 0; i < n; i++) {
+ uint64_t last = 0, p;
+
+ if (show_progress)
+ draw_progress(0xC000 + (0x3FFF * i / n), last_usec);
+
+ p = le64toh(f->data_hash_table[i].head_hash_offset);
+ while (p != 0) {
+ Object *o;
+ uint64_t next;
+
+ if (!contains_uint64(f->mmap, data_fd, n_data, p)) {
+ log_error("Invalid data object at hash entry %llu of %llu",
+ (unsigned long long) i, (unsigned long long) n);
+ return -EBADMSG;
+ }
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+ if (r < 0)
+ return r;
+
+ next = le64toh(o->data.next_hash_offset);
+ if (next != 0 && next <= p) {
+ log_error("Hash chain has a cycle in hash entry %llu of %llu",
+ (unsigned long long) i, (unsigned long long) n);
+ return -EBADMSG;
+ }
+
+ if (le64toh(o->data.hash) % n != i) {
+ log_error("Hash value mismatch in hash entry %llu of %llu",
+ (unsigned long long) i, (unsigned long long) n);
+ return -EBADMSG;
+ }
+
+ r = verify_data(f, o, p, entry_fd, n_entries, entry_array_fd, n_entry_arrays);
+ if (r < 0)
+ return r;
+
+ last = p;
+ p = next;
+ }
+
+ if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) {
+ log_error("Tail hash pointer mismatch in hash table");
+ return -EBADMSG;
+ }
+ }
+
+ return 0;
+}
+
+static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) {
+ uint64_t n, h, q;
+ int r;
+ assert(f);
+
+ n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
+ h = hash % n;
+
+ q = le64toh(f->data_hash_table[h].head_hash_offset);
+ while (q != 0) {
+ Object *o;
+
+ if (p == q)
+ return 1;
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, q, &o);
+ if (r < 0)
+ return r;
+
+ q = le64toh(o->data.next_hash_offset);
+ }
+
+ return 0;
+}
+
+static int verify_entry(
+ JournalFile *f,
+ Object *o, uint64_t p,
+ int data_fd, uint64_t n_data) {
+
+ uint64_t i, n;
+ int r;
+
+ assert(f);
+ assert(o);
+ assert(data_fd >= 0);
+
+ n = journal_file_entry_n_items(o);
+ for (i = 0; i < n; i++) {
+ uint64_t q, h;
+ Object *u;
+
+ q = le64toh(o->entry.items[i].object_offset);
+ h = le64toh(o->entry.items[i].hash);
+
+ if (!contains_uint64(f->mmap, data_fd, n_data, q)) {
+ log_error("Invalid data object at entry %llu",
+ (unsigned long long) p);
+ return -EBADMSG;
+ }
+
+ r = journal_file_move_to_object(f, OBJECT_DATA, q, &u);
+ if (r < 0)
+ return r;
+
+ if (le64toh(u->data.hash) != h) {
+ log_error("Hash mismatch for data object at entry %llu",
+ (unsigned long long) p);
+ return -EBADMSG;
+ }
+
+ r = data_object_in_hash_table(f, h, q);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_error("Data object missing from hash at entry %llu",
+ (unsigned long long) p);
+ return -EBADMSG;
+ }
+ }
+
+ return 0;
+}
+
+static int verify_entry_array(
+ JournalFile *f,
+ int data_fd, uint64_t n_data,
+ int entry_fd, uint64_t n_entries,
+ int entry_array_fd, uint64_t n_entry_arrays,
+ usec_t *last_usec,
+ bool show_progress) {
+
+ uint64_t i = 0, a, n, last = 0;
+ int r;
+
+ assert(f);
+ assert(data_fd >= 0);
+ assert(entry_fd >= 0);
+ assert(entry_array_fd >= 0);
+ assert(last_usec);
+
+ n = le64toh(f->header->n_entries);
+ a = le64toh(f->header->entry_array_offset);
+ while (i < n) {
+ uint64_t next, m, j;
+ Object *o;
+
+ if (show_progress)
+ draw_progress(0x8000 + (0x3FFF * i / n), last_usec);
+
+ if (a == 0) {
+ log_error("Array chain too short at %llu of %llu",
+ (unsigned long long) i, (unsigned long long) n);
+ return -EBADMSG;
+ }
+
+ if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
+ log_error("Invalid array at %llu of %llu",
+ (unsigned long long) i, (unsigned long long) n);
+ return -EBADMSG;
+ }
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
+ if (r < 0)
+ return r;
+
+ next = le64toh(o->entry_array.next_entry_array_offset);
+ if (next != 0 && next <= a) {
+ log_error("Array chain has cycle at %llu of %llu",
+ (unsigned long long) i, (unsigned long long) n);
+ return -EBADMSG;
+ }
+
+ m = journal_file_entry_array_n_items(o);
+ for (j = 0; i < n && j < m; i++, j++) {
+ uint64_t p;
+
+ p = le64toh(o->entry_array.items[j]);
+ if (p <= last) {
+ log_error("Entry array not sorted at %llu of %llu",
+ (unsigned long long) i, (unsigned long long) n);
+ return -EBADMSG;
+ }
+ last = p;
+
+ if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) {
+ log_error("Invalid array entry at %llu of %llu",
+ (unsigned long long) i, (unsigned long long) n);
+ return -EBADMSG;
+ }
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
+ if (r < 0)
+ return r;
+
+ r = verify_entry(f, o, p, data_fd, n_data);
+ if (r < 0)
+ return r;
+
+ /* Pointer might have moved, reposition */
+ r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
+ if (r < 0)
+ return r;
+ }
+
+ a = next;
+ }
+
+ return 0;
+}
+
+int journal_file_verify(
+ JournalFile *f,
+ const char *key,
+ usec_t *first_contained, usec_t *last_validated, usec_t *last_contained,
+ bool show_progress) {
+ int r;
+ Object *o;
+ uint64_t p = 0, last_epoch = 0, last_tag_realtime = 0, last_sealed_realtime = 0;
+
+ uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0;
+ sd_id128_t entry_boot_id;
+ bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false;
+ uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_entry_arrays = 0, n_tags = 0;
+ usec_t last_usec = 0;
+ int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
+ char data_path[] = "/var/tmp/journal-data-XXXXXX",
+ entry_path[] = "/var/tmp/journal-entry-XXXXXX",
+ entry_array_path[] = "/var/tmp/journal-entry-array-XXXXXX";
+ unsigned i;
+ bool found_last;
+#ifdef HAVE_GCRYPT
+ uint64_t last_tag = 0;
+#endif
+ assert(f);
+
+ if (key) {
+#ifdef HAVE_GCRYPT
+ r = journal_file_parse_verification_key(f, key);
+ if (r < 0) {
+ log_error("Failed to parse seed.");
+ return r;
+ }
+#else
+ return -ENOTSUP;
+#endif
+ } else if (f->seal)
+ return -ENOKEY;
+
+ data_fd = mkostemp(data_path, O_CLOEXEC);
+ if (data_fd < 0) {
+ log_error("Failed to create data file: %m");
+ r = -errno;
+ goto fail;
+ }
+ unlink(data_path);
+
+ entry_fd = mkostemp(entry_path, O_CLOEXEC);
+ if (entry_fd < 0) {
+ log_error("Failed to create entry file: %m");
+ r = -errno;
+ goto fail;
+ }
+ unlink(entry_path);
+
+ entry_array_fd = mkostemp(entry_array_path, O_CLOEXEC);
+ if (entry_array_fd < 0) {
+ log_error("Failed to create entry array file: %m");
+ r = -errno;
+ goto fail;
+ }
+ unlink(entry_array_path);
+
+#ifdef HAVE_GCRYPT
+ if ((le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SEALED) != 0)
+#else
+ if (f->header->compatible_flags != 0)
+#endif
+ {
+ log_error("Cannot verify file with unknown extensions.");
+ r = -ENOTSUP;
+ goto fail;
+ }
+
+ for (i = 0; i < sizeof(f->header->reserved); i++)
+ if (f->header->reserved[i] != 0) {
+ log_error("Reserved field in non-zero.");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ /* First iteration: we go through all objects, verify the
+ * superficial structure, headers, hashes. */
+
+ p = le64toh(f->header->header_size);
+ while (p != 0) {
+ if (show_progress)
+ draw_progress(0x7FFF * p / le64toh(f->header->tail_object_offset), &last_usec);
+
+ r = journal_file_move_to_object(f, -1, p, &o);
+ if (r < 0) {
+ log_error("Invalid object at %llu", (unsigned long long) p);
+ goto fail;
+ }
+
+ if (p > le64toh(f->header->tail_object_offset)) {
+ log_error("Invalid tail object pointer");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (p == le64toh(f->header->tail_object_offset))
+ found_last = true;
+
+ n_objects ++;
+
+ r = journal_file_object_verify(f, o);
+ if (r < 0) {
+ log_error("Invalid object contents at %llu", (unsigned long long) p);
+ goto fail;
+ }
+
+ if ((o->object.flags & OBJECT_COMPRESSED) && !JOURNAL_HEADER_COMPRESSED(f->header)) {
+ log_error("Compressed object in file without compression at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ switch (o->object.type) {
+
+ case OBJECT_DATA:
+ r = write_uint64(data_fd, p);
+ if (r < 0)
+ goto fail;
+
+ n_data++;
+ break;
+
+ case OBJECT_FIELD:
+ n_fields++;
+ break;
+
+ case OBJECT_ENTRY:
+ if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) {
+ log_error("First entry before first tag at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ r = write_uint64(entry_fd, p);
+ if (r < 0)
+ goto fail;
+
+ if (le64toh(o->entry.realtime) < last_tag_realtime) {
+ log_error("Older entry after newer tag at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (!entry_seqnum_set &&
+ le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
+ log_error("Head entry sequence number incorrect at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (entry_seqnum_set &&
+ entry_seqnum >= le64toh(o->entry.seqnum)) {
+ log_error("Entry sequence number out of synchronization at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ entry_seqnum = le64toh(o->entry.seqnum);
+ entry_seqnum_set = true;
+
+ if (entry_monotonic_set &&
+ sd_id128_equal(entry_boot_id, o->entry.boot_id) &&
+ entry_monotonic > le64toh(o->entry.monotonic)) {
+ log_error("Entry timestamp out of synchronization at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ entry_monotonic = le64toh(o->entry.monotonic);
+ entry_boot_id = o->entry.boot_id;
+ entry_monotonic_set = true;
+
+ if (!entry_realtime_set &&
+ le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) {
+ log_error("Head entry realtime timestamp incorrect");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ entry_realtime = le64toh(o->entry.realtime);
+ entry_realtime_set = true;
+
+ n_entries ++;
+ break;
+
+ case OBJECT_DATA_HASH_TABLE:
+ if (n_data_hash_tables > 1) {
+ log_error("More than one data hash table at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) ||
+ le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
+ log_error("Header fields for data hash table invalid");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ n_data_hash_tables++;
+ break;
+
+ case OBJECT_FIELD_HASH_TABLE:
+ if (n_field_hash_tables > 1) {
+ log_error("More than one field hash table at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) ||
+ le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
+ log_error("Header fields for field hash table invalid");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ n_field_hash_tables++;
+ break;
+
+ case OBJECT_ENTRY_ARRAY:
+ r = write_uint64(entry_array_fd, p);
+ if (r < 0)
+ goto fail;
+
+ if (p == le64toh(f->header->entry_array_offset)) {
+ if (found_main_entry_array) {
+ log_error("More than one main entry array at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ found_main_entry_array = true;
+ }
+
+ n_entry_arrays++;
+ break;
+
+ case OBJECT_TAG:
+ if (!JOURNAL_HEADER_SEALED(f->header)) {
+ log_error("Tag object in file without sealing at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (le64toh(o->tag.seqnum) != n_tags + 1) {
+ log_error("Tag sequence number out of synchronization at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (le64toh(o->tag.epoch) < last_epoch) {
+ log_error("Epoch sequence out of synchronization at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+#ifdef HAVE_GCRYPT
+ if (f->seal) {
+ uint64_t q, rt;
+
+ log_debug("Checking tag %llu..", (unsigned long long) le64toh(o->tag.seqnum));
+
+ rt = f->fss_start_usec + o->tag.epoch * f->fss_interval_usec;
+ if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) {
+ log_error("Tag/entry realtime timestamp out of synchronization at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ /* OK, now we know the epoch. So let's now set
+ * it, and calculate the HMAC for everything
+ * since the last tag. */
+ r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch));
+ if (r < 0)
+ goto fail;
+
+ r = journal_file_hmac_start(f);
+ if (r < 0)
+ goto fail;
+
+ if (last_tag == 0) {
+ r = journal_file_hmac_put_header(f);
+ if (r < 0)
+ goto fail;
+
+ q = le64toh(f->header->header_size);
+ } else
+ q = last_tag;
+
+ while (q <= p) {
+ r = journal_file_move_to_object(f, -1, q, &o);
+ if (r < 0)
+ goto fail;
+
+ r = journal_file_hmac_put_object(f, -1, o, q);
+ if (r < 0)
+ goto fail;
+
+ q = q + ALIGN64(le64toh(o->object.size));
+ }
+
+ /* Position might have changed, let's reposition things */
+ r = journal_file_move_to_object(f, -1, p, &o);
+ if (r < 0)
+ goto fail;
+
+ if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) {
+ log_error("Tag failed verification at %llu", (unsigned long long) p);
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ f->hmac_running = false;
+ last_tag_realtime = rt;
+ last_sealed_realtime = entry_realtime;
+ }
+
+ last_tag = p + ALIGN64(le64toh(o->object.size));
+#endif
+
+ last_epoch = le64toh(o->tag.epoch);
+
+ n_tags ++;
+ break;
+
+ default:
+ n_weird ++;
+ }
+
+ if (p == le64toh(f->header->tail_object_offset))
+ p = 0;
+ else
+ p = p + ALIGN64(le64toh(o->object.size));
+ }
+
+ if (!found_last) {
+ log_error("Tail object pointer dead");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (n_objects != le64toh(f->header->n_objects)) {
+ log_error("Object number mismatch");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (n_entries != le64toh(f->header->n_entries)) {
+ log_error("Entry number mismatch");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
+ n_data != le64toh(f->header->n_data)) {
+ log_error("Data number mismatch");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
+ n_fields != le64toh(f->header->n_fields)) {
+ log_error("Field number mismatch");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) &&
+ n_tags != le64toh(f->header->n_tags)) {
+ log_error("Tag number mismatch");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) &&
+ n_entry_arrays != le64toh(f->header->n_entry_arrays)) {
+ log_error("Entry array number mismatch");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (n_data_hash_tables != 1) {
+ log_error("Missing data hash table");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (n_field_hash_tables != 1) {
+ log_error("Missing field hash table");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (!found_main_entry_array) {
+ log_error("Missing entry array");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (entry_seqnum_set &&
+ entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
+ log_error("Invalid tail seqnum");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (entry_monotonic_set &&
+ (!sd_id128_equal(entry_boot_id, f->header->boot_id) ||
+ entry_monotonic != le64toh(f->header->tail_entry_monotonic))) {
+ log_error("Invalid tail monotonic timestamp");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
+ log_error("Invalid tail realtime timestamp");
+ r = -EBADMSG;
+ goto fail;
+ }
+
+ /* Second iteration: we follow all objects referenced from the
+ * two entry points: the object hash table and the entry
+ * array. We also check that everything referenced (directly
+ * or indirectly) in the data hash table also exists in the
+ * entry array, and vice versa. Note that we do not care for
+ * unreferenced objects. We only care that everything that is
+ * referenced is consistent. */
+
+ r = verify_entry_array(f,
+ data_fd, n_data,
+ entry_fd, n_entries,
+ entry_array_fd, n_entry_arrays,
+ &last_usec,
+ show_progress);
+ if (r < 0)
+ goto fail;
+
+ r = verify_hash_table(f,
+ data_fd, n_data,
+ entry_fd, n_entries,
+ entry_array_fd, n_entry_arrays,
+ &last_usec,
+ show_progress);
+ if (r < 0)
+ goto fail;
+
+ if (show_progress)
+ flush_progress();
+
+ mmap_cache_close_fd(f->mmap, data_fd);
+ mmap_cache_close_fd(f->mmap, entry_fd);
+ mmap_cache_close_fd(f->mmap, entry_array_fd);
+
+ close_nointr_nofail(data_fd);
+ close_nointr_nofail(entry_fd);
+ close_nointr_nofail(entry_array_fd);
+
+ if (first_contained)
+ *first_contained = le64toh(f->header->head_entry_realtime);
+ if (last_validated)
+ *last_validated = last_sealed_realtime;
+ if (last_contained)
+ *last_contained = le64toh(f->header->tail_entry_realtime);
+
+ return 0;
+
+fail:
+ if (show_progress)
+ flush_progress();
+
+ log_error("File corruption detected at %s:%llu (of %llu, %llu%%).",
+ f->path,
+ (unsigned long long) p,
+ (unsigned long long) f->last_stat.st_size,
+ (unsigned long long) (100 * p / f->last_stat.st_size));
+
+ if (data_fd >= 0) {
+ mmap_cache_close_fd(f->mmap, data_fd);
+ close_nointr_nofail(data_fd);
+ }
+
+ if (entry_fd >= 0) {
+ mmap_cache_close_fd(f->mmap, entry_fd);
+ close_nointr_nofail(entry_fd);
+ }
+
+ if (entry_array_fd >= 0) {
+ mmap_cache_close_fd(f->mmap, entry_array_fd);
+ close_nointr_nofail(entry_array_fd);
+ }
+
+ return r;
+}
diff --git a/src/journal/journal-verify.h b/src/journal/journal-verify.h
new file mode 100644
index 0000000000..e392ab61d7
--- /dev/null
+++ b/src/journal/journal-verify.h
@@ -0,0 +1,26 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journal-file.h"
+
+int journal_file_verify(JournalFile *f, const char *key, usec_t *first_contained, usec_t *last_validated, usec_t *last_contained, bool show_progress);
diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
new file mode 100644
index 0000000000..cccd8a7692
--- /dev/null
+++ b/src/journal/journalctl.c
@@ -0,0 +1,1060 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <locale.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/poll.h>
+#include <time.h>
+#include <getopt.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+
+#include <systemd/sd-journal.h>
+
+#include "log.h"
+#include "util.h"
+#include "path-util.h"
+#include "build.h"
+#include "pager.h"
+#include "logs-show.h"
+#include "strv.h"
+#include "journal-internal.h"
+#include "journal-def.h"
+#include "journal-verify.h"
+#include "journal-authenticate.h"
+#include "journal-qrcode.h"
+#include "fsprg.h"
+#include "unit-name.h"
+
+#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
+
+static OutputMode arg_output = OUTPUT_SHORT;
+static bool arg_follow = false;
+static bool arg_all = false;
+static bool arg_no_pager = false;
+static unsigned arg_lines = 0;
+static bool arg_no_tail = false;
+static bool arg_quiet = false;
+static bool arg_merge = false;
+static bool arg_this_boot = false;
+static const char *arg_cursor = NULL;
+static const char *arg_directory = NULL;
+static int arg_priorities = 0xFF;
+static const char *arg_verify_key = NULL;
+#ifdef HAVE_GCRYPT
+static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
+#endif
+static usec_t arg_since, arg_until;
+static bool arg_since_set = false, arg_until_set = false;
+static const char *arg_unit = NULL;
+static const char *arg_field = NULL;
+
+static enum {
+ ACTION_SHOW,
+ ACTION_NEW_ID128,
+ ACTION_PRINT_HEADER,
+ ACTION_SETUP_KEYS,
+ ACTION_VERIFY,
+ ACTION_DISK_USAGE,
+} arg_action = ACTION_SHOW;
+
+static int help(void) {
+
+ printf("%s [OPTIONS...] [MATCHES...]\n\n"
+ "Query the journal.\n\n"
+ "Flags:\n"
+ " --since=DATE Start showing entries newer or of the specified date\n"
+ " --until=DATE Stop showing entries older or of the specified date\n"
+ " -c --cursor=CURSOR Start showing entries from specified cursor\n"
+ " -b --this-boot Show data only from current boot\n"
+ " -u --unit=UNIT Show data only from the specified unit\n"
+ " -p --priority=RANGE Show only messages within the specified priority range\n"
+ " -f --follow Follow journal\n"
+ " -n --lines[=INTEGER] Number of journal entries to show\n"
+ " --no-tail Show all lines, even in follow mode\n"
+ " -o --output=STRING Change journal output mode (short, short-monotonic,\n"
+ " verbose, export, json, json-pretty, json-sse, cat)\n"
+ " -a --all Show all fields, including long and unprintable\n"
+ " -q --quiet Don't show privilege warning\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " -m --merge Show entries from all available journals\n"
+ " -D --directory=PATH Show journal files from directory\n"
+#ifdef HAVE_GCRYPT
+ " --interval=TIME Time interval for changing the FSS sealing key\n"
+ " --verify-key=KEY Specify FSS verification key\n"
+#endif
+ "\nCommands:\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --new-id128 Generate a new 128 Bit ID\n"
+ " --header Show journal header information\n"
+ " --disk-usage Show total disk usage\n"
+ " -F --field=FIELD List all values a certain field takes\n"
+#ifdef HAVE_GCRYPT
+ " --setup-keys Generate new FSS key pair\n"
+ " --verify Verify journal file consistency\n"
+#endif
+ , program_invocation_short_name);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ ARG_NO_TAIL,
+ ARG_NEW_ID128,
+ ARG_HEADER,
+ ARG_SETUP_KEYS,
+ ARG_INTERVAL,
+ ARG_VERIFY,
+ ARG_VERIFY_KEY,
+ ARG_DISK_USAGE,
+ ARG_SINCE,
+ ARG_UNTIL
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version" , no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "follow", no_argument, NULL, 'f' },
+ { "output", required_argument, NULL, 'o' },
+ { "all", no_argument, NULL, 'a' },
+ { "lines", optional_argument, NULL, 'n' },
+ { "no-tail", no_argument, NULL, ARG_NO_TAIL },
+ { "new-id128", no_argument, NULL, ARG_NEW_ID128 },
+ { "quiet", no_argument, NULL, 'q' },
+ { "merge", no_argument, NULL, 'm' },
+ { "this-boot", no_argument, NULL, 'b' },
+ { "directory", required_argument, NULL, 'D' },
+ { "header", no_argument, NULL, ARG_HEADER },
+ { "priority", no_argument, NULL, 'p' },
+ { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS },
+ { "interval", required_argument, NULL, ARG_INTERVAL },
+ { "verify", no_argument, NULL, ARG_VERIFY },
+ { "verify-key", required_argument, NULL, ARG_VERIFY_KEY },
+ { "disk-usage", no_argument, NULL, ARG_DISK_USAGE },
+ { "cursor", required_argument, NULL, 'c' },
+ { "since", required_argument, NULL, ARG_SINCE },
+ { "until", required_argument, NULL, ARG_UNTIL },
+ { "unit", required_argument, NULL, 'u' },
+ { "field", required_argument, NULL, 'F' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hfo:an::qmbD:p:c:u:F:", options, NULL)) >= 0) {
+
+ switch (c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ puts(PACKAGE_STRING);
+ puts(DISTRIBUTION);
+ puts(SYSTEMD_FEATURES);
+ return 0;
+
+ case ARG_NO_PAGER:
+ arg_no_pager = true;
+ break;
+
+ case 'f':
+ arg_follow = true;
+ break;
+
+ case 'o':
+ arg_output = output_mode_from_string(optarg);
+ if (arg_output < 0) {
+ log_error("Unknown output format '%s'.", optarg);
+ return -EINVAL;
+ }
+
+ if (arg_output == OUTPUT_EXPORT ||
+ arg_output == OUTPUT_JSON ||
+ arg_output == OUTPUT_JSON_PRETTY ||
+ arg_output == OUTPUT_JSON_SSE ||
+ arg_output == OUTPUT_CAT)
+ arg_quiet = true;
+
+ break;
+
+ case 'a':
+ arg_all = true;
+ break;
+
+ case 'n':
+ if (optarg) {
+ r = safe_atou(optarg, &arg_lines);
+ if (r < 0 || arg_lines <= 0) {
+ log_error("Failed to parse lines '%s'", optarg);
+ return -EINVAL;
+ }
+ } else
+ arg_lines = 10;
+
+ break;
+
+ case ARG_NO_TAIL:
+ arg_no_tail = true;
+ break;
+
+ case ARG_NEW_ID128:
+ arg_action = ACTION_NEW_ID128;
+ break;
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
+ case 'm':
+ arg_merge = true;
+ break;
+
+ case 'b':
+ arg_this_boot = true;
+ break;
+
+ case 'D':
+ arg_directory = optarg;
+ break;
+
+ case 'c':
+ arg_cursor = optarg;
+ break;
+
+ case ARG_HEADER:
+ arg_action = ACTION_PRINT_HEADER;
+ break;
+
+ case ARG_VERIFY:
+ arg_action = ACTION_VERIFY;
+ break;
+
+ case ARG_DISK_USAGE:
+ arg_action = ACTION_DISK_USAGE;
+ break;
+
+#ifdef HAVE_GCRYPT
+ case ARG_SETUP_KEYS:
+ arg_action = ACTION_SETUP_KEYS;
+ break;
+
+
+ case ARG_VERIFY_KEY:
+ arg_action = ACTION_VERIFY;
+ arg_verify_key = optarg;
+ arg_merge = false;
+ break;
+
+ case ARG_INTERVAL:
+ r = parse_usec(optarg, &arg_interval);
+ if (r < 0 || arg_interval <= 0) {
+ log_error("Failed to parse sealing key change interval: %s", optarg);
+ return -EINVAL;
+ }
+ break;
+#else
+ case ARG_SETUP_KEYS:
+ case ARG_VERIFY_KEY:
+ case ARG_INTERVAL:
+ log_error("Forward-secure sealing not available.");
+ return -ENOTSUP;
+#endif
+
+ case 'p': {
+ const char *dots;
+
+ dots = strstr(optarg, "..");
+ if (dots) {
+ char *a;
+ int from, to, i;
+
+ /* a range */
+ a = strndup(optarg, dots - optarg);
+ if (!a)
+ return log_oom();
+
+ from = log_level_from_string(a);
+ to = log_level_from_string(dots + 2);
+ free(a);
+
+ if (from < 0 || to < 0) {
+ log_error("Failed to parse log level range %s", optarg);
+ return -EINVAL;
+ }
+
+ arg_priorities = 0;
+
+ if (from < to) {
+ for (i = from; i <= to; i++)
+ arg_priorities |= 1 << i;
+ } else {
+ for (i = to; i <= from; i++)
+ arg_priorities |= 1 << i;
+ }
+
+ } else {
+ int p, i;
+
+ p = log_level_from_string(optarg);
+ if (p < 0) {
+ log_error("Unknown log level %s", optarg);
+ return -EINVAL;
+ }
+
+ arg_priorities = 0;
+
+ for (i = 0; i <= p; i++)
+ arg_priorities |= 1 << i;
+ }
+
+ break;
+ }
+
+ case ARG_SINCE:
+ r = parse_timestamp(optarg, &arg_since);
+ if (r < 0) {
+ log_error("Failed to parse timestamp: %s", optarg);
+ return -EINVAL;
+ }
+ arg_since_set = true;
+ break;
+
+ case ARG_UNTIL:
+ r = parse_timestamp(optarg, &arg_until);
+ if (r < 0) {
+ log_error("Failed to parse timestamp: %s", optarg);
+ return -EINVAL;
+ }
+ arg_until_set = true;
+ break;
+
+ case 'u':
+ arg_unit = optarg;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ case 'F':
+ arg_field = optarg;
+ break;
+
+ default:
+ log_error("Unknown option code %c", c);
+ return -EINVAL;
+ }
+ }
+
+ if (arg_follow && !arg_no_tail && arg_lines <= 0)
+ arg_lines = 10;
+
+ if (arg_since_set && arg_until_set && arg_since_set > arg_until_set) {
+ log_error("--since= must be before --until=.");
+ return -EINVAL;
+ }
+
+ if (arg_cursor && arg_since_set) {
+ log_error("Please specify either --since= or --cursor=, not both.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+static int generate_new_id128(void) {
+ sd_id128_t id;
+ int r;
+ unsigned i;
+
+ r = sd_id128_randomize(&id);
+ if (r < 0) {
+ log_error("Failed to generate ID: %s", strerror(-r));
+ return r;
+ }
+
+ printf("As string:\n"
+ SD_ID128_FORMAT_STR "\n\n"
+ "As UUID:\n"
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
+ "As macro:\n"
+ "#define MESSAGE_XYZ SD_ID128_MAKE(",
+ SD_ID128_FORMAT_VAL(id),
+ SD_ID128_FORMAT_VAL(id));
+
+ for (i = 0; i < 16; i++)
+ printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
+
+ fputs(")\n", stdout);
+
+ return 0;
+}
+
+static int add_matches(sd_journal *j, char **args) {
+ char **i;
+ int r;
+
+ assert(j);
+
+ STRV_FOREACH(i, args) {
+
+ if (streq(*i, "+"))
+ r = sd_journal_add_disjunction(j);
+ else if (path_is_absolute(*i)) {
+ char *p, *t = NULL;
+ const char *path;
+ struct stat st;
+
+ p = canonicalize_file_name(*i);
+ path = p ? p : *i;
+
+ if (stat(path, &st) < 0) {
+ free(p);
+ log_error("Couldn't stat file: %m");
+ return -errno;
+ }
+
+ if (S_ISREG(st.st_mode) && (0111 & st.st_mode))
+ t = strappend("_EXE=", path);
+ else if (S_ISCHR(st.st_mode))
+ asprintf(&t, "_KERNEL_DEVICE=c%u:%u", major(st.st_rdev), minor(st.st_rdev));
+ else if (S_ISBLK(st.st_mode))
+ asprintf(&t, "_KERNEL_DEVICE=b%u:%u", major(st.st_rdev), minor(st.st_rdev));
+ else {
+ free(p);
+ log_error("File is not a device node, regular file or is not executable: %s", *i);
+ return -EINVAL;
+ }
+
+ free(p);
+
+ if (!t)
+ return log_oom();
+
+ r = sd_journal_add_match(j, t, 0);
+ free(t);
+ } else
+ r = sd_journal_add_match(j, *i, 0);
+
+ if (r < 0) {
+ log_error("Failed to add match '%s': %s", *i, strerror(-r));
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int add_this_boot(sd_journal *j) {
+ char match[9+32+1] = "_BOOT_ID=";
+ sd_id128_t boot_id;
+ int r;
+
+ assert(j);
+
+ if (!arg_this_boot)
+ return 0;
+
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0) {
+ log_error("Failed to get boot id: %s", strerror(-r));
+ return r;
+ }
+
+ sd_id128_to_string(boot_id, match + 9);
+ r = sd_journal_add_match(j, match, strlen(match));
+ if (r < 0) {
+ log_error("Failed to add match: %s", strerror(-r));
+ return r;
+ }
+
+ return 0;
+}
+
+static int add_unit(sd_journal *j) {
+ _cleanup_free_ char *m = NULL, *u = NULL;
+ int r;
+
+ assert(j);
+
+ if (isempty(arg_unit))
+ return 0;
+
+ u = unit_name_mangle(arg_unit);
+ if (!u)
+ return log_oom();
+
+ m = strappend("_SYSTEMD_UNIT=", u);
+ if (!m)
+ return log_oom();
+
+ r = sd_journal_add_match(j, m, strlen(m));
+ if (r < 0) {
+ log_error("Failed to add match: %s", strerror(-r));
+ return r;
+ }
+
+ return 0;
+}
+
+static int add_priorities(sd_journal *j) {
+ char match[] = "PRIORITY=0";
+ int i, r;
+
+ assert(j);
+
+ if (arg_priorities == 0xFF)
+ return 0;
+
+ for (i = LOG_EMERG; i <= LOG_DEBUG; i++)
+ if (arg_priorities & (1 << i)) {
+ match[sizeof(match)-2] = '0' + i;
+
+ r = sd_journal_add_match(j, match, strlen(match));
+ if (r < 0) {
+ log_error("Failed to add match: %s", strerror(-r));
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int setup_keys(void) {
+#ifdef HAVE_GCRYPT
+ size_t mpk_size, seed_size, state_size, i;
+ uint8_t *mpk, *seed, *state;
+ ssize_t l;
+ int fd = -1, r, attr = 0;
+ sd_id128_t machine, boot;
+ char *p = NULL, *k = NULL;
+ struct FSSHeader h;
+ uint64_t n;
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0) {
+ log_error("Failed to get machine ID: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_id128_get_boot(&boot);
+ if (r < 0) {
+ log_error("Failed to get boot ID: %s", strerror(-r));
+ return r;
+ }
+
+ if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
+ SD_ID128_FORMAT_VAL(machine)) < 0)
+ return log_oom();
+
+ if (access(p, F_OK) >= 0) {
+ log_error("Sealing key file %s exists already.", p);
+ r = -EEXIST;
+ goto finish;
+ }
+
+ if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX",
+ SD_ID128_FORMAT_VAL(machine)) < 0) {
+ r = log_oom();
+ goto finish;
+ }
+
+ mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR);
+ mpk = alloca(mpk_size);
+
+ seed_size = FSPRG_RECOMMENDED_SEEDLEN;
+ seed = alloca(seed_size);
+
+ state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
+ state = alloca(state_size);
+
+ fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0) {
+ log_error("Failed to open /dev/random: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ log_info("Generating seed...");
+ l = loop_read(fd, seed, seed_size, true);
+ if (l < 0 || (size_t) l != seed_size) {
+ log_error("Failed to read random seed: %s", strerror(EIO));
+ r = -EIO;
+ goto finish;
+ }
+
+ log_info("Generating key pair...");
+ FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR);
+
+ log_info("Generating sealing key...");
+ FSPRG_GenState0(state, mpk, seed, seed_size);
+
+ assert(arg_interval > 0);
+
+ n = now(CLOCK_REALTIME);
+ n /= arg_interval;
+
+ close_nointr_nofail(fd);
+ fd = mkostemp(k, O_WRONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0) {
+ log_error("Failed to open %s: %m", k);
+ r = -errno;
+ goto finish;
+ }
+
+ /* Enable secure remove, exclusion from dump, synchronous
+ * writing and in-place updating */
+ if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0)
+ log_warning("FS_IOC_GETFLAGS failed: %m");
+
+ attr |= FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL;
+
+ if (ioctl(fd, FS_IOC_SETFLAGS, &attr) < 0)
+ log_warning("FS_IOC_SETFLAGS failed: %m");
+
+ zero(h);
+ memcpy(h.signature, "KSHHRHLP", 8);
+ h.machine_id = machine;
+ h.boot_id = boot;
+ h.header_size = htole64(sizeof(h));
+ h.start_usec = htole64(n * arg_interval);
+ h.interval_usec = htole64(arg_interval);
+ h.fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR);
+ h.fsprg_state_size = htole64(state_size);
+
+ l = loop_write(fd, &h, sizeof(h), false);
+ if (l < 0 || (size_t) l != sizeof(h)) {
+ log_error("Failed to write header: %s", strerror(EIO));
+ r = -EIO;
+ goto finish;
+ }
+
+ l = loop_write(fd, state, state_size, false);
+ if (l < 0 || (size_t) l != state_size) {
+ log_error("Failed to write state: %s", strerror(EIO));
+ r = -EIO;
+ goto finish;
+ }
+
+ if (link(k, p) < 0) {
+ log_error("Failed to link file: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ if (on_tty()) {
+ fprintf(stderr,
+ "\n"
+ "The new key pair has been generated. The " ANSI_HIGHLIGHT_ON "secret sealing key" ANSI_HIGHLIGHT_OFF " has been written to\n"
+ "the following local file. This key file is automatically updated when the\n"
+ "sealing key is advanced. It should not be used on multiple hosts.\n"
+ "\n"
+ "\t%s\n"
+ "\n"
+ "Please write down the following " ANSI_HIGHLIGHT_ON "secret verification key" ANSI_HIGHLIGHT_OFF ". It should be stored\n"
+ "at a safe location and should not be saved locally on disk.\n"
+ "\n\t" ANSI_HIGHLIGHT_RED_ON, p);
+ fflush(stderr);
+ }
+ for (i = 0; i < seed_size; i++) {
+ if (i > 0 && i % 3 == 0)
+ putchar('-');
+ printf("%02x", ((uint8_t*) seed)[i]);
+ }
+
+ printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) arg_interval);
+
+ if (on_tty()) {
+ char tsb[FORMAT_TIMESPAN_MAX], *hn;
+
+ fprintf(stderr,
+ ANSI_HIGHLIGHT_OFF "\n"
+ "The sealing key is automatically changed every %s.\n",
+ format_timespan(tsb, sizeof(tsb), arg_interval));
+
+ hn = gethostname_malloc();
+
+ if (hn) {
+ hostname_cleanup(hn);
+ fprintf(stderr, "\nThe keys have been generated for host %s/" SD_ID128_FORMAT_STR ".\n", hn, SD_ID128_FORMAT_VAL(machine));
+ } else
+ fprintf(stderr, "\nThe keys have been generated for host " SD_ID128_FORMAT_STR ".\n", SD_ID128_FORMAT_VAL(machine));
+
+#ifdef HAVE_QRENCODE
+ /* If this is not an UTF-8 system don't print any QR codes */
+ if (is_locale_utf8()) {
+ fputs("\nTo transfer the verification key to your phone please scan the QR code below:\n\n", stderr);
+ print_qr_code(stderr, seed, seed_size, n, arg_interval, hn, machine);
+ }
+#endif
+ free(hn);
+ }
+
+ r = 0;
+
+finish:
+ if (fd >= 0)
+ close_nointr_nofail(fd);
+
+ if (k) {
+ unlink(k);
+ free(k);
+ }
+
+ free(p);
+
+ return r;
+#else
+ log_error("Forward-secure sealing not available.");
+ return -ENOTSUP;
+#endif
+}
+
+static int verify(sd_journal *j) {
+ int r = 0;
+ Iterator i;
+ JournalFile *f;
+
+ assert(j);
+
+ log_show_color(true);
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ int k;
+ usec_t first, validated, last;
+
+#ifdef HAVE_GCRYPT
+ if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header))
+ log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path);
+#endif
+
+ k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, true);
+ if (k == -EINVAL) {
+ /* If the key was invalid give up right-away. */
+ return k;
+ } else if (k < 0) {
+ log_warning("FAIL: %s (%s)", f->path, strerror(-k));
+ r = k;
+ } else {
+ char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX], c[FORMAT_TIMESPAN_MAX];
+ log_info("PASS: %s", f->path);
+
+ if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) {
+ if (validated > 0) {
+ log_info("=> Validated from %s to %s, final %s entries not sealed.",
+ format_timestamp(a, sizeof(a), first),
+ format_timestamp(b, sizeof(b), validated),
+ format_timespan(c, sizeof(c), last > validated ? last - validated : 0));
+ } else if (last > 0)
+ log_info("=> No sealing yet, %s of entries not sealed.",
+ format_timespan(c, sizeof(c), last - first));
+ else
+ log_info("=> No sealing yet, no entries in file.");
+ }
+ }
+ }
+
+ return r;
+}
+
+static int access_check(void) {
+
+#ifdef HAVE_ACL
+ if (access("/var/log/journal", F_OK) < 0 && geteuid() != 0 && in_group("adm") <= 0) {
+ log_error("Unprivileged users can't see messages unless persistent log storage is enabled. Users in the group 'adm' can always see messages.");
+ return -EACCES;
+ }
+
+ if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
+ log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this notice off.");
+#else
+ if (geteuid() != 0 && in_group("adm") <= 0) {
+ log_error("No access to messages. Only users in the group 'adm' can see messages.");
+ return -EACCES;
+ }
+#endif
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+ sd_journal *j = NULL;
+ bool need_seek = false;
+ sd_id128_t previous_boot_id;
+ bool previous_boot_id_valid = false;
+ unsigned n_shown = 0;
+
+ setlocale(LC_ALL, "");
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ signal(SIGWINCH, columns_lines_cache_reset);
+
+ if (arg_action == ACTION_NEW_ID128) {
+ r = generate_new_id128();
+ goto finish;
+ }
+
+ if (arg_action == ACTION_SETUP_KEYS) {
+ r = setup_keys();
+ goto finish;
+ }
+
+ r = access_check();
+ if (r < 0)
+ goto finish;
+
+ if (arg_directory)
+ r = sd_journal_open_directory(&j, arg_directory, 0);
+ else
+ r = sd_journal_open(&j, arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY);
+ if (r < 0) {
+ log_error("Failed to open journal: %s", strerror(-r));
+ goto finish;
+ }
+
+ if (arg_action == ACTION_VERIFY) {
+ r = verify(j);
+ goto finish;
+ }
+
+ if (arg_action == ACTION_PRINT_HEADER) {
+ journal_print_header(j);
+ r = 0;
+ goto finish;
+ }
+
+ if (arg_action == ACTION_DISK_USAGE) {
+ uint64_t bytes;
+ char sbytes[FORMAT_BYTES_MAX];
+
+ r = sd_journal_get_usage(j, &bytes);
+ if (r < 0)
+ goto finish;
+
+ printf("Journals take up %s on disk.\n", format_bytes(sbytes, sizeof(sbytes), bytes));
+ r = 0;
+ goto finish;
+ }
+
+ r = add_this_boot(j);
+ if (r < 0)
+ goto finish;
+
+ r = add_unit(j);
+ if (r < 0)
+ goto finish;
+
+ r = add_matches(j, argv + optind);
+ if (r < 0)
+ goto finish;
+
+ r = add_priorities(j);
+ if (r < 0)
+ goto finish;
+
+ if (arg_field) {
+ const void *data;
+ size_t size;
+
+ r = sd_journal_query_unique(j, arg_field);
+ if (r < 0) {
+ log_error("Failed to query unique data objects: %s", strerror(-r));
+ goto finish;
+ }
+
+ SD_JOURNAL_FOREACH_UNIQUE(j, data, size) {
+ const void *eq;
+
+ if (arg_lines > 0 && n_shown >= arg_lines)
+ break;
+
+ eq = memchr(data, '=', size);
+ if (eq)
+ printf("%.*s\n", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1);
+ else
+ printf("%.*s\n", (int) size, (const char*) data);
+
+ n_shown ++;
+ }
+
+ r = 0;
+ goto finish;
+ }
+
+ if (arg_cursor) {
+ r = sd_journal_seek_cursor(j, arg_cursor);
+ if (r < 0) {
+ log_error("Failed to seek to cursor: %s", strerror(-r));
+ goto finish;
+ }
+
+ r = sd_journal_next(j);
+
+ } else if (arg_since_set) {
+ r = sd_journal_seek_realtime_usec(j, arg_since);
+ if (r < 0) {
+ log_error("Failed to seek to date: %s", strerror(-r));
+ goto finish;
+ }
+ r = sd_journal_next(j);
+
+ } else if (arg_lines > 0) {
+ r = sd_journal_seek_tail(j);
+ if (r < 0) {
+ log_error("Failed to seek to tail: %s", strerror(-r));
+ goto finish;
+ }
+
+ r = sd_journal_previous_skip(j, arg_lines);
+
+ } else {
+ r = sd_journal_seek_head(j);
+ if (r < 0) {
+ log_error("Failed to seek to head: %s", strerror(-r));
+ goto finish;
+ }
+
+ r = sd_journal_next(j);
+ }
+
+ if (r < 0) {
+ log_error("Failed to iterate through journal: %s", strerror(-r));
+ goto finish;
+ }
+
+ if (!arg_no_pager && !arg_follow)
+ pager_open();
+
+ if (!arg_quiet) {
+ usec_t start, end;
+ char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
+
+ r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
+ if (r < 0) {
+ log_error("Failed to get cutoff: %s", strerror(-r));
+ goto finish;
+ }
+
+ if (r > 0) {
+ if (arg_follow)
+ printf("-- Logs begin at %s. --\n",
+ format_timestamp(start_buf, sizeof(start_buf), start));
+ else
+ printf("-- Logs begin at %s, end at %s. --\n",
+ format_timestamp(start_buf, sizeof(start_buf), start),
+ format_timestamp(end_buf, sizeof(end_buf), end));
+ }
+ }
+
+ for (;;) {
+ while (arg_lines == 0 || arg_follow || n_shown < arg_lines) {
+ int flags;
+
+ if (need_seek) {
+ r = sd_journal_next(j);
+ if (r < 0) {
+ log_error("Failed to iterate through journal: %s", strerror(-r));
+ goto finish;
+ }
+ }
+
+ if (r == 0)
+ break;
+
+ if (arg_until_set) {
+ usec_t usec;
+
+ r = sd_journal_get_realtime_usec(j, &usec);
+ if (r < 0) {
+ log_error("Failed to determine timestamp: %s", strerror(-r));
+ goto finish;
+ }
+ }
+
+ if (!arg_merge) {
+ sd_id128_t boot_id;
+
+ r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
+ if (r >= 0) {
+ if (previous_boot_id_valid &&
+ !sd_id128_equal(boot_id, previous_boot_id))
+ printf(ANSI_HIGHLIGHT_ON "-- Reboot --" ANSI_HIGHLIGHT_OFF "\n");
+
+ previous_boot_id = boot_id;
+ previous_boot_id_valid = true;
+ }
+ }
+
+ flags =
+ arg_all * OUTPUT_SHOW_ALL |
+ (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH |
+ on_tty() * OUTPUT_COLOR;
+
+ r = output_journal(stdout, j, arg_output, 0, flags);
+ if (r < 0)
+ goto finish;
+
+ need_seek = true;
+ n_shown++;
+ }
+
+ if (!arg_follow)
+ break;
+
+ r = sd_journal_wait(j, (uint64_t) -1);
+ if (r < 0) {
+ log_error("Couldn't wait for journal event: %s", strerror(-r));
+ goto finish;
+ }
+ }
+
+finish:
+ if (j)
+ sd_journal_close(j);
+
+ pager_close();
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/journal/journald-console.c b/src/journal/journald-console.c
new file mode 100644
index 0000000000..be55f94878
--- /dev/null
+++ b/src/journal/journald-console.c
@@ -0,0 +1,86 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include "journald-server.h"
+#include "journald-console.h"
+
+void server_forward_console(
+ Server *s,
+ int priority,
+ const char *identifier,
+ const char *message,
+ struct ucred *ucred) {
+
+ struct iovec iovec[4];
+ char header_pid[16];
+ int n = 0, fd;
+ char *ident_buf = NULL;
+ const char *tty;
+
+ assert(s);
+ assert(message);
+
+ if (LOG_PRI(priority) > s->max_level_console)
+ return;
+
+ /* First: identifier and PID */
+ if (ucred) {
+ if (!identifier) {
+ get_process_comm(ucred->pid, &ident_buf);
+ identifier = ident_buf;
+ }
+
+ snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid);
+ char_array_0(header_pid);
+
+ if (identifier)
+ IOVEC_SET_STRING(iovec[n++], identifier);
+
+ IOVEC_SET_STRING(iovec[n++], header_pid);
+ } else if (identifier) {
+ IOVEC_SET_STRING(iovec[n++], identifier);
+ IOVEC_SET_STRING(iovec[n++], ": ");
+ }
+
+ /* Third: message */
+ IOVEC_SET_STRING(iovec[n++], message);
+ IOVEC_SET_STRING(iovec[n++], "\n");
+
+ tty = s->tty_path ? s->tty_path : "/dev/console";
+
+ fd = open_terminal(tty, O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0) {
+ log_debug("Failed to open %s for logging: %s", tty, strerror(errno));
+ goto finish;
+ }
+
+ if (writev(fd, iovec, n) < 0)
+ log_debug("Failed to write to %s for logging: %s", tty, strerror(errno));
+
+ close_nointr_nofail(fd);
+
+finish:
+ free(ident_buf);
+}
diff --git a/src/journal/journald-console.h b/src/journal/journald-console.h
new file mode 100644
index 0000000000..aa8e6579ba
--- /dev/null
+++ b/src/journal/journald-console.h
@@ -0,0 +1,26 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journald-server.h"
+
+void server_forward_console(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred);
diff --git a/src/journal/journald-gperf.gperf b/src/journal/journald-gperf.gperf
new file mode 100644
index 0000000000..1baef1411c
--- /dev/null
+++ b/src/journal/journald-gperf.gperf
@@ -0,0 +1,39 @@
+%{
+#include <stddef.h>
+#include <sys/socket.h>
+#include "conf-parser.h"
+#include "journald-server.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name journald_gperf_hash
+%define lookup-function-name journald_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Journal.Storage, config_parse_storage, 0, offsetof(Server, storage)
+Journal.Compress, config_parse_bool, 0, offsetof(Server, compress)
+Journal.Seal, config_parse_bool, 0, offsetof(Server, seal)
+Journal.RateLimitInterval, config_parse_usec, 0, offsetof(Server, rate_limit_interval)
+Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, rate_limit_burst)
+Journal.SystemMaxUse, config_parse_bytes_off, 0, offsetof(Server, system_metrics.max_use)
+Journal.SystemMaxFileSize, config_parse_bytes_off, 0, offsetof(Server, system_metrics.max_size)
+Journal.SystemKeepFree, config_parse_bytes_off, 0, offsetof(Server, system_metrics.keep_free)
+Journal.RuntimeMaxUse, config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.max_use)
+Journal.RuntimeMaxFileSize, config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.max_size)
+Journal.RuntimeKeepFree, config_parse_bytes_off, 0, offsetof(Server, runtime_metrics.keep_free)
+Journal.MaxRetentionSec, config_parse_usec, 0, offsetof(Server, max_retention_usec)
+Journal.MaxFileSec, config_parse_usec, 0, offsetof(Server, max_file_usec)
+Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog)
+Journal.ForwardToKMsg, config_parse_bool, 0, offsetof(Server, forward_to_kmsg)
+Journal.ForwardToConsole, config_parse_bool, 0, offsetof(Server, forward_to_console)
+Journal.TTYPath, config_parse_path, 0, offsetof(Server, tty_path)
+Journal.MaxLevelStore, config_parse_level, 0, offsetof(Server, max_level_store)
+Journal.MaxLevelSyslog, config_parse_level, 0, offsetof(Server, max_level_syslog)
+Journal.MaxLevelKMsg, config_parse_level, 0, offsetof(Server, max_level_kmsg)
+Journal.MaxLevelConsole, config_parse_level, 0, offsetof(Server, max_level_console)
+Journal.SplitMode, config_parse_split_mode,0, offsetof(Server, split_mode)
diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c
new file mode 100644
index 0000000000..b8198760d6
--- /dev/null
+++ b/src/journal/journald-kmsg.c
@@ -0,0 +1,438 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+
+#include <systemd/sd-messages.h>
+#include <libudev.h>
+
+#include "journald-server.h"
+#include "journald-kmsg.h"
+#include "journald-syslog.h"
+
+void server_forward_kmsg(
+ Server *s,
+ int priority,
+ const char *identifier,
+ const char *message,
+ struct ucred *ucred) {
+
+ struct iovec iovec[5];
+ char header_priority[6], header_pid[16];
+ int n = 0;
+ char *ident_buf = NULL;
+
+ assert(s);
+ assert(priority >= 0);
+ assert(priority <= 999);
+ assert(message);
+
+ if (_unlikely_(LOG_PRI(priority) > s->max_level_kmsg))
+ return;
+
+ if (_unlikely_(s->dev_kmsg_fd < 0))
+ return;
+
+ /* Never allow messages with kernel facility to be written to
+ * kmsg, regardless where the data comes from. */
+ priority = syslog_fixup_facility(priority);
+
+ /* First: priority field */
+ snprintf(header_priority, sizeof(header_priority), "<%i>", priority);
+ char_array_0(header_priority);
+ IOVEC_SET_STRING(iovec[n++], header_priority);
+
+ /* Second: identifier and PID */
+ if (ucred) {
+ if (!identifier) {
+ get_process_comm(ucred->pid, &ident_buf);
+ identifier = ident_buf;
+ }
+
+ snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid);
+ char_array_0(header_pid);
+
+ if (identifier)
+ IOVEC_SET_STRING(iovec[n++], identifier);
+
+ IOVEC_SET_STRING(iovec[n++], header_pid);
+ } else if (identifier) {
+ IOVEC_SET_STRING(iovec[n++], identifier);
+ IOVEC_SET_STRING(iovec[n++], ": ");
+ }
+
+ /* Fourth: message */
+ IOVEC_SET_STRING(iovec[n++], message);
+ IOVEC_SET_STRING(iovec[n++], "\n");
+
+ if (writev(s->dev_kmsg_fd, iovec, n) < 0)
+ log_debug("Failed to write to /dev/kmsg for logging: %s", strerror(errno));
+
+ free(ident_buf);
+}
+
+static bool is_us(const char *pid) {
+ pid_t t;
+
+ assert(pid);
+
+ if (parse_pid(pid, &t) < 0)
+ return false;
+
+ return t == getpid();
+}
+
+static void dev_kmsg_record(Server *s, char *p, size_t l) {
+ struct iovec iovec[N_IOVEC_META_FIELDS + 7 + N_IOVEC_KERNEL_FIELDS + 2 + N_IOVEC_UDEV_FIELDS];
+ char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL;
+ int priority, r;
+ unsigned n = 0, z = 0, j;
+ unsigned long long usec;
+ char *identifier = NULL, *pid = NULL, *e, *f, *k;
+ uint64_t serial;
+ size_t pl;
+ char *kernel_device = NULL;
+
+ assert(s);
+ assert(p);
+
+ if (l <= 0)
+ return;
+
+ e = memchr(p, ',', l);
+ if (!e)
+ return;
+ *e = 0;
+
+ r = safe_atoi(p, &priority);
+ if (r < 0 || priority < 0 || priority > 999)
+ return;
+
+ if (s->forward_to_kmsg && (priority & LOG_FACMASK) != LOG_KERN)
+ return;
+
+ l -= (e - p) + 1;
+ p = e + 1;
+ e = memchr(p, ',', l);
+ if (!e)
+ return;
+ *e = 0;
+
+ r = safe_atou64(p, &serial);
+ if (r < 0)
+ return;
+
+ if (s->kernel_seqnum) {
+ /* We already read this one? */
+ if (serial < *s->kernel_seqnum)
+ return;
+
+ /* Did we lose any? */
+ if (serial > *s->kernel_seqnum)
+ server_driver_message(s, SD_MESSAGE_JOURNAL_MISSED, "Missed %llu kernel messages", (unsigned long long) serial - *s->kernel_seqnum - 1);
+
+ /* Make sure we never read this one again. Note that
+ * we always store the next message serial we expect
+ * here, simply because this makes handling the first
+ * message with serial 0 easy. */
+ *s->kernel_seqnum = serial + 1;
+ }
+
+ l -= (e - p) + 1;
+ p = e + 1;
+ f = memchr(p, ';', l);
+ if (!f)
+ return;
+ /* Kernel 3.6 has the flags field, kernel 3.5 lacks that */
+ e = memchr(p, ',', l);
+ if (!e || f < e)
+ e = f;
+ *e = 0;
+
+ r = safe_atollu(p, &usec);
+ if (r < 0)
+ return;
+
+ l -= (f - p) + 1;
+ p = f + 1;
+ e = memchr(p, '\n', l);
+ if (!e)
+ return;
+ *e = 0;
+
+ pl = e - p;
+ l -= (e - p) + 1;
+ k = e + 1;
+
+ for (j = 0; l > 0 && j < N_IOVEC_KERNEL_FIELDS; j++) {
+ char *m;
+ /* Meta data fields attached */
+
+ if (*k != ' ')
+ break;
+
+ k ++, l --;
+
+ e = memchr(k, '\n', l);
+ if (!e)
+ return;
+
+ *e = 0;
+
+ m = cunescape_length_with_prefix(k, e - k, "_KERNEL_");
+ if (!m)
+ break;
+
+ if (startswith(m, "_KERNEL_DEVICE="))
+ kernel_device = m + 15;
+
+ IOVEC_SET_STRING(iovec[n++], m);
+ z++;
+
+ l -= (e - k) + 1;
+ k = e + 1;
+ }
+
+ if (kernel_device) {
+ struct udev_device *ud;
+
+ ud = udev_device_new_from_device_id(s->udev, kernel_device);
+ if (ud) {
+ const char *g;
+ struct udev_list_entry *ll;
+ char *b;
+
+ g = udev_device_get_devnode(ud);
+ if (g) {
+ b = strappend("_UDEV_DEVNODE=", g);
+ if (b) {
+ IOVEC_SET_STRING(iovec[n++], b);
+ z++;
+ }
+ }
+
+ g = udev_device_get_sysname(ud);
+ if (g) {
+ b = strappend("_UDEV_SYSNAME=", g);
+ if (b) {
+ IOVEC_SET_STRING(iovec[n++], b);
+ z++;
+ }
+ }
+
+ j = 0;
+ ll = udev_device_get_devlinks_list_entry(ud);
+ udev_list_entry_foreach(ll, ll) {
+
+ if (j > N_IOVEC_UDEV_FIELDS)
+ break;
+
+ g = udev_list_entry_get_name(ll);
+ b = strappend("_UDEV_DEVLINK=", g);
+ if (g) {
+ IOVEC_SET_STRING(iovec[n++], b);
+ z++;
+ }
+
+ j++;
+ }
+
+ udev_device_unref(ud);
+ }
+ }
+
+ if (asprintf(&source_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu", usec) >= 0)
+ IOVEC_SET_STRING(iovec[n++], source_time);
+
+ IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=kernel");
+
+ if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0)
+ IOVEC_SET_STRING(iovec[n++], syslog_priority);
+
+ if ((priority & LOG_FACMASK) == LOG_KERN)
+ IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=kernel");
+ else {
+ pl -= syslog_parse_identifier((const char**) &p, &identifier, &pid);
+
+ /* Avoid any messages we generated ourselves via
+ * log_info() and friends. */
+ if (pid && is_us(pid))
+ goto finish;
+
+ if (identifier) {
+ syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier);
+ if (syslog_identifier)
+ IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+ }
+
+ if (pid) {
+ syslog_pid = strappend("SYSLOG_PID=", pid);
+ if (syslog_pid)
+ IOVEC_SET_STRING(iovec[n++], syslog_pid);
+ }
+
+ if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0)
+ IOVEC_SET_STRING(iovec[n++], syslog_facility);
+ }
+
+ message = cunescape_length_with_prefix(p, pl, "MESSAGE=");
+ if (message)
+ IOVEC_SET_STRING(iovec[n++], message);
+
+ server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority);
+
+finish:
+ for (j = 0; j < z; j++)
+ free(iovec[j].iov_base);
+
+ free(message);
+ free(syslog_priority);
+ free(syslog_identifier);
+ free(syslog_pid);
+ free(syslog_facility);
+ free(source_time);
+ free(identifier);
+ free(pid);
+}
+
+int server_read_dev_kmsg(Server *s) {
+ char buffer[8192+1]; /* the kernel-side limit per record is 8K currently */
+ ssize_t l;
+
+ assert(s);
+ assert(s->dev_kmsg_fd >= 0);
+
+ l = read(s->dev_kmsg_fd, buffer, sizeof(buffer) - 1);
+ if (l == 0)
+ return 0;
+ if (l < 0) {
+ /* Old kernels who don't allow reading from /dev/kmsg
+ * return EINVAL when we try. So handle this cleanly,
+ * but don' try to ever read from it again. */
+ if (errno == EINVAL) {
+ epoll_ctl(s->epoll_fd, EPOLL_CTL_DEL, s->dev_kmsg_fd, NULL);
+ return 0;
+ }
+
+ if (errno == EAGAIN || errno == EINTR || errno == EPIPE)
+ return 0;
+
+ log_error("Failed to read from kernel: %m");
+ return -errno;
+ }
+
+ dev_kmsg_record(s, buffer, l);
+ return 1;
+}
+
+int server_flush_dev_kmsg(Server *s) {
+ int r;
+
+ assert(s);
+
+ if (s->dev_kmsg_fd < 0)
+ return 0;
+
+ if (!s->dev_kmsg_readable)
+ return 0;
+
+ log_debug("Flushing /dev/kmsg...");
+
+ for (;;) {
+ r = server_read_dev_kmsg(s);
+ if (r < 0)
+ return r;
+
+ if (r == 0)
+ break;
+ }
+
+ return 0;
+}
+
+int server_open_dev_kmsg(Server *s) {
+ struct epoll_event ev;
+
+ assert(s);
+
+ s->dev_kmsg_fd = open("/dev/kmsg", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+ if (s->dev_kmsg_fd < 0) {
+ log_warning("Failed to open /dev/kmsg, ignoring: %m");
+ return 0;
+ }
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.fd = s->dev_kmsg_fd;
+ if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->dev_kmsg_fd, &ev) < 0) {
+
+ /* This will fail with EPERM on older kernels where
+ * /dev/kmsg is not readable. */
+ if (errno == EPERM)
+ return 0;
+
+ log_error("Failed to add /dev/kmsg fd to epoll object: %m");
+ return -errno;
+ }
+
+ s->dev_kmsg_readable = true;
+
+ return 0;
+}
+
+int server_open_kernel_seqnum(Server *s) {
+ int fd;
+ uint64_t *p;
+
+ assert(s);
+
+ /* We store the seqnum we last read in an mmaped file. That
+ * way we can just use it like a variable, but it is
+ * persistent and automatically flushed at reboot. */
+
+ fd = open("/run/systemd/journal/kernel-seqnum", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644);
+ if (fd < 0) {
+ log_error("Failed to open /run/systemd/journal/kernel-seqnum, ignoring: %m");
+ return 0;
+ }
+
+ if (posix_fallocate(fd, 0, sizeof(uint64_t)) < 0) {
+ log_error("Failed to allocate sequential number file, ignoring: %m");
+ close_nointr_nofail(fd);
+ return 0;
+ }
+
+ p = mmap(NULL, sizeof(uint64_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ if (p == MAP_FAILED) {
+ log_error("Failed to map sequential number file, ignoring: %m");
+ close_nointr_nofail(fd);
+ return 0;
+ }
+
+ close_nointr_nofail(fd);
+ s->kernel_seqnum = p;
+
+ return 0;
+}
diff --git a/src/journal/journald-kmsg.h b/src/journal/journald-kmsg.h
new file mode 100644
index 0000000000..f287161329
--- /dev/null
+++ b/src/journal/journald-kmsg.h
@@ -0,0 +1,32 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journald-server.h"
+
+int server_open_dev_kmsg(Server *s);
+int server_read_dev_kmsg(Server *s);
+int server_flush_dev_kmsg(Server *s);
+
+void server_forward_kmsg(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred);
+
+int server_open_kernel_seqnum(Server *s);
diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c
new file mode 100644
index 0000000000..ac3183ece3
--- /dev/null
+++ b/src/journal/journald-native.c
@@ -0,0 +1,420 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stddef.h>
+#include <sys/epoll.h>
+
+#include "socket-util.h"
+#include "path-util.h"
+#include "journald-server.h"
+#include "journald-native.h"
+#include "journald-kmsg.h"
+#include "journald-console.h"
+#include "journald-syslog.h"
+
+#define ENTRY_SIZE_MAX (1024*1024*64)
+#define DATA_SIZE_MAX (1024*1024*64)
+
+static bool valid_user_field(const char *p, size_t l) {
+ const char *a;
+
+ /* We kinda enforce POSIX syntax recommendations for
+ environment variables here, but make a couple of additional
+ requirements.
+
+ http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html */
+
+ /* No empty field names */
+ if (l <= 0)
+ return false;
+
+ /* Don't allow names longer than 64 chars */
+ if (l > 64)
+ return false;
+
+ /* Variables starting with an underscore are protected */
+ if (p[0] == '_')
+ return false;
+
+ /* Don't allow digits as first character */
+ if (p[0] >= '0' && p[0] <= '9')
+ return false;
+
+ /* Only allow A-Z0-9 and '_' */
+ for (a = p; a < p + l; a++)
+ if (!((*a >= 'A' && *a <= 'Z') ||
+ (*a >= '0' && *a <= '9') ||
+ *a == '_'))
+ return false;
+
+ return true;
+}
+
+void server_process_native_message(
+ Server *s,
+ const void *buffer, size_t buffer_size,
+ struct ucred *ucred,
+ struct timeval *tv,
+ const char *label, size_t label_len) {
+
+ struct iovec *iovec = NULL;
+ unsigned n = 0, m = 0, j, tn = (unsigned) -1;
+ const char *p;
+ size_t remaining;
+ int priority = LOG_INFO;
+ char *identifier = NULL, *message = NULL;
+
+ assert(s);
+ assert(buffer || buffer_size == 0);
+
+ p = buffer;
+ remaining = buffer_size;
+
+ while (remaining > 0) {
+ const char *e, *q;
+
+ e = memchr(p, '\n', remaining);
+
+ if (!e) {
+ /* Trailing noise, let's ignore it, and flush what we collected */
+ log_debug("Received message with trailing noise, ignoring.");
+ break;
+ }
+
+ if (e == p) {
+ /* Entry separator */
+ server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority);
+ n = 0;
+ priority = LOG_INFO;
+
+ p++;
+ remaining--;
+ continue;
+ }
+
+ if (*p == '.' || *p == '#') {
+ /* Ignore control commands for now, and
+ * comments too. */
+ remaining -= (e - p) + 1;
+ p = e + 1;
+ continue;
+ }
+
+ /* A property follows */
+
+ if (n+N_IOVEC_META_FIELDS >= m) {
+ struct iovec *c;
+ unsigned u;
+
+ u = MAX((n+N_IOVEC_META_FIELDS+1) * 2U, 4U);
+ c = realloc(iovec, u * sizeof(struct iovec));
+ if (!c) {
+ log_oom();
+ break;
+ }
+
+ iovec = c;
+ m = u;
+ }
+
+ q = memchr(p, '=', e - p);
+ if (q) {
+ if (valid_user_field(p, q - p)) {
+ size_t l;
+
+ l = e - p;
+
+ /* If the field name starts with an
+ * underscore, skip the variable,
+ * since that indidates a trusted
+ * field */
+ iovec[n].iov_base = (char*) p;
+ iovec[n].iov_len = l;
+ n++;
+
+ /* We need to determine the priority
+ * of this entry for the rate limiting
+ * logic */
+ if (l == 10 &&
+ memcmp(p, "PRIORITY=", 9) == 0 &&
+ p[9] >= '0' && p[9] <= '9')
+ priority = (priority & LOG_FACMASK) | (p[9] - '0');
+
+ else if (l == 17 &&
+ memcmp(p, "SYSLOG_FACILITY=", 16) == 0 &&
+ p[16] >= '0' && p[16] <= '9')
+ priority = (priority & LOG_PRIMASK) | ((p[16] - '0') << 3);
+
+ else if (l == 18 &&
+ memcmp(p, "SYSLOG_FACILITY=", 16) == 0 &&
+ p[16] >= '0' && p[16] <= '9' &&
+ p[17] >= '0' && p[17] <= '9')
+ priority = (priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3);
+
+ else if (l >= 19 &&
+ memcmp(p, "SYSLOG_IDENTIFIER=", 18) == 0) {
+ char *t;
+
+ t = strndup(p + 18, l - 18);
+ if (t) {
+ free(identifier);
+ identifier = t;
+ }
+ } else if (l >= 8 &&
+ memcmp(p, "MESSAGE=", 8) == 0) {
+ char *t;
+
+ t = strndup(p + 8, l - 8);
+ if (t) {
+ free(message);
+ message = t;
+ }
+ }
+ }
+
+ remaining -= (e - p) + 1;
+ p = e + 1;
+ continue;
+ } else {
+ le64_t l_le;
+ uint64_t l;
+ char *k;
+
+ if (remaining < e - p + 1 + sizeof(uint64_t) + 1) {
+ log_debug("Failed to parse message, ignoring.");
+ break;
+ }
+
+ memcpy(&l_le, e + 1, sizeof(uint64_t));
+ l = le64toh(l_le);
+
+ if (l > DATA_SIZE_MAX) {
+ log_debug("Received binary data block too large, ignoring.");
+ break;
+ }
+
+ if ((uint64_t) remaining < e - p + 1 + sizeof(uint64_t) + l + 1 ||
+ e[1+sizeof(uint64_t)+l] != '\n') {
+ log_debug("Failed to parse message, ignoring.");
+ break;
+ }
+
+ k = malloc((e - p) + 1 + l);
+ if (!k) {
+ log_oom();
+ break;
+ }
+
+ memcpy(k, p, e - p);
+ k[e - p] = '=';
+ memcpy(k + (e - p) + 1, e + 1 + sizeof(uint64_t), l);
+
+ if (valid_user_field(p, e - p)) {
+ iovec[n].iov_base = k;
+ iovec[n].iov_len = (e - p) + 1 + l;
+ n++;
+ } else
+ free(k);
+
+ remaining -= (e - p) + 1 + sizeof(uint64_t) + l + 1;
+ p = e + 1 + sizeof(uint64_t) + l + 1;
+ }
+ }
+
+ if (n <= 0)
+ goto finish;
+
+ tn = n++;
+ IOVEC_SET_STRING(iovec[tn], "_TRANSPORT=journal");
+
+ if (message) {
+ if (s->forward_to_syslog)
+ server_forward_syslog(s, priority, identifier, message, ucred, tv);
+
+ if (s->forward_to_kmsg)
+ server_forward_kmsg(s, priority, identifier, message, ucred);
+
+ if (s->forward_to_console)
+ server_forward_console(s, priority, identifier, message, ucred);
+ }
+
+ server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority);
+
+finish:
+ for (j = 0; j < n; j++) {
+ if (j == tn)
+ continue;
+
+ if (iovec[j].iov_base < buffer ||
+ (const uint8_t*) iovec[j].iov_base >= (const uint8_t*) buffer + buffer_size)
+ free(iovec[j].iov_base);
+ }
+
+ free(iovec);
+ free(identifier);
+ free(message);
+}
+
+void server_process_native_file(
+ Server *s,
+ int fd,
+ struct ucred *ucred,
+ struct timeval *tv,
+ const char *label, size_t label_len) {
+
+ struct stat st;
+ _cleanup_free_ void *p = NULL;
+ ssize_t n;
+ int r;
+
+ assert(s);
+ assert(fd >= 0);
+
+ if (!ucred || ucred->uid != 0) {
+ _cleanup_free_ char *sl = NULL, *k = NULL;
+ const char *e;
+
+ if (asprintf(&sl, "/proc/self/fd/%i", fd) < 0) {
+ log_oom();
+ return;
+ }
+
+ r = readlink_malloc(sl, &k);
+ if (r < 0) {
+ log_error("readlink(%s) failed: %m", sl);
+ return;
+ }
+
+ e = path_startswith(k, "/dev/shm/");
+ if (!e)
+ e = path_startswith(k, "/tmp/");
+ if (!e)
+ e = path_startswith(k, "/var/tmp/");
+ if (!e) {
+ log_error("Received file outside of allowed directories. Refusing.");
+ return;
+ }
+
+ if (!filename_is_safe(e)) {
+ log_error("Received file in subdirectory of allowed directories. Refusing.");
+ return;
+ }
+ }
+
+ /* Data is in the passed file, since it didn't fit in a
+ * datagram. We can't map the file here, since clients might
+ * then truncate it and trigger a SIGBUS for us. So let's
+ * stupidly read it */
+
+ if (fstat(fd, &st) < 0) {
+ log_error("Failed to stat passed file, ignoring: %m");
+ return;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ log_error("File passed is not regular. Ignoring.");
+ return;
+ }
+
+ if (st.st_size <= 0)
+ return;
+
+ if (st.st_size > ENTRY_SIZE_MAX) {
+ log_error("File passed too large. Ignoring.");
+ return;
+ }
+
+ p = malloc(st.st_size);
+ if (!p) {
+ log_oom();
+ return;
+ }
+
+ n = pread(fd, p, st.st_size, 0);
+ if (n < 0)
+ log_error("Failed to read file, ignoring: %s", strerror(-n));
+ else if (n > 0)
+ server_process_native_message(s, p, n, ucred, tv, label, label_len);
+}
+
+int server_open_native_socket(Server*s) {
+ union sockaddr_union sa;
+ int one, r;
+ struct epoll_event ev;
+
+ assert(s);
+
+ if (s->native_fd < 0) {
+
+ s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (s->native_fd < 0) {
+ log_error("socket() failed: %m");
+ return -errno;
+ }
+
+ zero(sa);
+ sa.un.sun_family = AF_UNIX;
+ strncpy(sa.un.sun_path, "/run/systemd/journal/socket", sizeof(sa.un.sun_path));
+
+ unlink(sa.un.sun_path);
+
+ r = bind(s->native_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
+ if (r < 0) {
+ log_error("bind() failed: %m");
+ return -errno;
+ }
+
+ chmod(sa.un.sun_path, 0666);
+ } else
+ fd_nonblock(s->native_fd, 1);
+
+ one = 1;
+ r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ if (r < 0) {
+ log_error("SO_PASSCRED failed: %m");
+ return -errno;
+ }
+
+#ifdef HAVE_SELINUX
+ one = 1;
+ r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one));
+ if (r < 0)
+ log_warning("SO_PASSSEC failed: %m");
+#endif
+
+ one = 1;
+ r = setsockopt(s->native_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
+ if (r < 0) {
+ log_error("SO_TIMESTAMP failed: %m");
+ return -errno;
+ }
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.fd = s->native_fd;
+ if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->native_fd, &ev) < 0) {
+ log_error("Failed to add native server fd to epoll object: %m");
+ return -errno;
+ }
+
+ return 0;
+}
diff --git a/src/journal/journald-native.h b/src/journal/journald-native.h
new file mode 100644
index 0000000000..16c09f523e
--- /dev/null
+++ b/src/journal/journald-native.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journald-server.h"
+
+void server_process_native_message(Server *s, const void *buffer, size_t buffer_size, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len);
+
+void server_process_native_file(Server *s, int fd, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len);
+
+int server_open_native_socket(Server*s);
diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c
new file mode 100644
index 0000000000..8bd68476a3
--- /dev/null
+++ b/src/journal/journald-rate-limit.c
@@ -0,0 +1,275 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <string.h>
+#include <errno.h>
+
+#include "journald-rate-limit.h"
+#include "list.h"
+#include "util.h"
+#include "hashmap.h"
+
+#define POOLS_MAX 5
+#define BUCKETS_MAX 127
+#define GROUPS_MAX 2047
+
+static const int priority_map[] = {
+ [LOG_EMERG] = 0,
+ [LOG_ALERT] = 0,
+ [LOG_CRIT] = 0,
+ [LOG_ERR] = 1,
+ [LOG_WARNING] = 2,
+ [LOG_NOTICE] = 3,
+ [LOG_INFO] = 3,
+ [LOG_DEBUG] = 4
+};
+
+typedef struct JournalRateLimitPool JournalRateLimitPool;
+typedef struct JournalRateLimitGroup JournalRateLimitGroup;
+
+struct JournalRateLimitPool {
+ usec_t begin;
+ unsigned num;
+ unsigned suppressed;
+};
+
+struct JournalRateLimitGroup {
+ JournalRateLimit *parent;
+
+ char *id;
+ JournalRateLimitPool pools[POOLS_MAX];
+ unsigned hash;
+
+ LIST_FIELDS(JournalRateLimitGroup, bucket);
+ LIST_FIELDS(JournalRateLimitGroup, lru);
+};
+
+struct JournalRateLimit {
+ usec_t interval;
+ unsigned burst;
+
+ JournalRateLimitGroup* buckets[BUCKETS_MAX];
+ JournalRateLimitGroup *lru, *lru_tail;
+
+ unsigned n_groups;
+};
+
+JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) {
+ JournalRateLimit *r;
+
+ assert(interval > 0 || burst == 0);
+
+ r = new0(JournalRateLimit, 1);
+ if (!r)
+ return NULL;
+
+ r->interval = interval;
+ r->burst = burst;
+
+ return r;
+}
+
+static void journal_rate_limit_group_free(JournalRateLimitGroup *g) {
+ assert(g);
+
+ if (g->parent) {
+ assert(g->parent->n_groups > 0);
+
+ if (g->parent->lru_tail == g)
+ g->parent->lru_tail = g->lru_prev;
+
+ LIST_REMOVE(JournalRateLimitGroup, lru, g->parent->lru, g);
+ LIST_REMOVE(JournalRateLimitGroup, bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g);
+
+ g->parent->n_groups --;
+ }
+
+ free(g->id);
+ free(g);
+}
+
+void journal_rate_limit_free(JournalRateLimit *r) {
+ assert(r);
+
+ while (r->lru)
+ journal_rate_limit_group_free(r->lru);
+
+ free(r);
+}
+
+static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, usec_t ts) {
+ unsigned i;
+
+ assert(g);
+
+ for (i = 0; i < POOLS_MAX; i++)
+ if (g->pools[i].begin + g->parent->interval >= ts)
+ return false;
+
+ return true;
+}
+
+static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) {
+ assert(r);
+
+ /* Makes room for at least one new item, but drop all
+ * expored items too. */
+
+ while (r->n_groups >= GROUPS_MAX ||
+ (r->lru_tail && journal_rate_limit_group_expired(r->lru_tail, ts)))
+ journal_rate_limit_group_free(r->lru_tail);
+}
+
+static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) {
+ JournalRateLimitGroup *g;
+
+ assert(r);
+ assert(id);
+
+ g = new0(JournalRateLimitGroup, 1);
+ if (!g)
+ return NULL;
+
+ g->id = strdup(id);
+ if (!g->id)
+ goto fail;
+
+ g->hash = string_hash_func(g->id);
+
+ journal_rate_limit_vacuum(r, ts);
+
+ LIST_PREPEND(JournalRateLimitGroup, bucket, r->buckets[g->hash % BUCKETS_MAX], g);
+ LIST_PREPEND(JournalRateLimitGroup, lru, r->lru, g);
+ if (!g->lru_next)
+ r->lru_tail = g;
+ r->n_groups ++;
+
+ g->parent = r;
+ return g;
+
+fail:
+ journal_rate_limit_group_free(g);
+ return NULL;
+}
+
+static uint64_t u64log2(uint64_t n) {
+ unsigned r;
+
+ if (n <= 1)
+ return 0;
+
+ r = 0;
+ for (;;) {
+ n = n >> 1;
+ if (!n)
+ return r;
+ r++;
+ }
+}
+
+static unsigned burst_modulate(unsigned burst, uint64_t available) {
+ unsigned k;
+
+ /* Modulates the burst rate a bit with the amount of available
+ * disk space */
+
+ k = u64log2(available);
+
+ /* 1MB */
+ if (k <= 20)
+ return burst;
+
+ burst = (burst * (k-20)) / 4;
+
+ /*
+ * Example:
+ *
+ * <= 1MB = rate * 1
+ * 16MB = rate * 2
+ * 256MB = rate * 3
+ * 4GB = rate * 4
+ * 64GB = rate * 5
+ * 1TB = rate * 6
+ */
+
+ return burst;
+}
+
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) {
+ unsigned h;
+ JournalRateLimitGroup *g;
+ JournalRateLimitPool *p;
+ unsigned burst;
+ usec_t ts;
+
+ assert(id);
+
+ if (!r)
+ return 1;
+
+ if (r->interval == 0 || r->burst == 0)
+ return 1;
+
+ burst = burst_modulate(r->burst, available);
+
+ ts = now(CLOCK_MONOTONIC);
+
+ h = string_hash_func(id);
+ g = r->buckets[h % BUCKETS_MAX];
+
+ LIST_FOREACH(bucket, g, g)
+ if (streq(g->id, id))
+ break;
+
+ if (!g) {
+ g = journal_rate_limit_group_new(r, id, ts);
+ if (!g)
+ return -ENOMEM;
+ }
+
+ p = &g->pools[priority_map[priority]];
+
+ if (p->begin <= 0) {
+ p->suppressed = 0;
+ p->num = 1;
+ p->begin = ts;
+ return 1;
+ }
+
+ if (p->begin + r->interval < ts) {
+ unsigned s;
+
+ s = p->suppressed;
+ p->suppressed = 0;
+ p->num = 1;
+ p->begin = ts;
+
+ return 1 + s;
+ }
+
+ if (p->num <= burst) {
+ p->num++;
+ return 1;
+ }
+
+ p->suppressed++;
+ return 0;
+}
diff --git a/src/journal/journald-rate-limit.h b/src/journal/journald-rate-limit.h
new file mode 100644
index 0000000000..648ab22786
--- /dev/null
+++ b/src/journal/journald-rate-limit.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "macro.h"
+#include "util.h"
+
+typedef struct JournalRateLimit JournalRateLimit;
+
+JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst);
+void journal_rate_limit_free(JournalRateLimit *r);
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available);
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
new file mode 100644
index 0000000000..0ff2866c11
--- /dev/null
+++ b/src/journal/journald-server.c
@@ -0,0 +1,1502 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/signalfd.h>
+#include <sys/ioctl.h>
+#include <linux/sockios.h>
+#include <sys/statvfs.h>
+#include <sys/mman.h>
+
+#include <libudev.h>
+#include <systemd/sd-journal.h>
+#include <systemd/sd-messages.h>
+#include <systemd/sd-daemon.h>
+
+#ifdef HAVE_LOGIND
+#include <systemd/sd-login.h>
+#endif
+
+#include "mkdir.h"
+#include "hashmap.h"
+#include "journal-file.h"
+#include "socket-util.h"
+#include "cgroup-util.h"
+#include "list.h"
+#include "virt.h"
+#include "missing.h"
+#include "conf-parser.h"
+#include "journal-internal.h"
+#include "journal-vacuum.h"
+#include "journal-authenticate.h"
+#include "journald-server.h"
+#include "journald-rate-limit.h"
+#include "journald-kmsg.h"
+#include "journald-syslog.h"
+#include "journald-stream.h"
+#include "journald-console.h"
+#include "journald-native.h"
+
+#ifdef HAVE_ACL
+#include <sys/acl.h>
+#include <acl/libacl.h>
+#include "acl-util.h"
+#endif
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#define USER_JOURNALS_MAX 1024
+
+#define DEFAULT_RATE_LIMIT_INTERVAL (10*USEC_PER_SEC)
+#define DEFAULT_RATE_LIMIT_BURST 200
+
+#define RECHECK_AVAILABLE_SPACE_USEC (30*USEC_PER_SEC)
+
+static const char* const storage_table[] = {
+ [STORAGE_AUTO] = "auto",
+ [STORAGE_VOLATILE] = "volatile",
+ [STORAGE_PERSISTENT] = "persistent",
+ [STORAGE_NONE] = "none"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(storage, Storage);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_storage, storage, Storage, "Failed to parse storage setting");
+
+static const char* const split_mode_table[] = {
+ [SPLIT_NONE] = "none",
+ [SPLIT_UID] = "uid",
+ [SPLIT_LOGIN] = "login"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(split_mode, SplitMode);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_split_mode, split_mode, SplitMode, "Failed to parse split mode setting");
+
+static uint64_t available_space(Server *s) {
+ char ids[33], *p;
+ const char *f;
+ sd_id128_t machine;
+ struct statvfs ss;
+ uint64_t sum = 0, avail = 0, ss_avail = 0;
+ int r;
+ DIR *d;
+ usec_t ts;
+ JournalMetrics *m;
+
+ ts = now(CLOCK_MONOTONIC);
+
+ if (s->cached_available_space_timestamp + RECHECK_AVAILABLE_SPACE_USEC > ts)
+ return s->cached_available_space;
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return 0;
+
+ if (s->system_journal) {
+ f = "/var/log/journal/";
+ m = &s->system_metrics;
+ } else {
+ f = "/run/log/journal/";
+ m = &s->runtime_metrics;
+ }
+
+ assert(m);
+
+ p = strappend(f, sd_id128_to_string(machine, ids));
+ if (!p)
+ return 0;
+
+ d = opendir(p);
+ free(p);
+
+ if (!d)
+ return 0;
+
+ if (fstatvfs(dirfd(d), &ss) < 0)
+ goto finish;
+
+ for (;;) {
+ struct stat st;
+ struct dirent *de;
+ union dirent_storage buf;
+
+ r = readdir_r(d, &buf.de, &de);
+ if (r != 0)
+ break;
+
+ if (!de)
+ break;
+
+ if (!endswith(de->d_name, ".journal") &&
+ !endswith(de->d_name, ".journal~"))
+ continue;
+
+ if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
+ continue;
+
+ if (!S_ISREG(st.st_mode))
+ continue;
+
+ sum += (uint64_t) st.st_blocks * 512UL;
+ }
+
+ avail = sum >= m->max_use ? 0 : m->max_use - sum;
+
+ ss_avail = ss.f_bsize * ss.f_bavail;
+
+ ss_avail = ss_avail < m->keep_free ? 0 : ss_avail - m->keep_free;
+
+ if (ss_avail < avail)
+ avail = ss_avail;
+
+ s->cached_available_space = avail;
+ s->cached_available_space_timestamp = ts;
+
+finish:
+ closedir(d);
+
+ return avail;
+}
+
+static void server_read_file_gid(Server *s) {
+ const char *adm = "adm";
+ int r;
+
+ assert(s);
+
+ if (s->file_gid_valid)
+ return;
+
+ r = get_group_creds(&adm, &s->file_gid);
+ if (r < 0)
+ log_warning("Failed to resolve 'adm' group: %s", strerror(-r));
+
+ /* if we couldn't read the gid, then it will be 0, but that's
+ * fine and we shouldn't try to resolve the group again, so
+ * let's just pretend it worked right-away. */
+ s->file_gid_valid = true;
+}
+
+void server_fix_perms(Server *s, JournalFile *f, uid_t uid) {
+ int r;
+#ifdef HAVE_ACL
+ acl_t acl;
+ acl_entry_t entry;
+ acl_permset_t permset;
+#endif
+
+ assert(f);
+
+ server_read_file_gid(s);
+
+ r = fchmod_and_fchown(f->fd, 0640, 0, s->file_gid);
+ if (r < 0)
+ log_warning("Failed to fix access mode/rights on %s, ignoring: %s", f->path, strerror(-r));
+
+#ifdef HAVE_ACL
+ if (uid <= 0)
+ return;
+
+ acl = acl_get_fd(f->fd);
+ if (!acl) {
+ log_warning("Failed to read ACL on %s, ignoring: %m", f->path);
+ return;
+ }
+
+ r = acl_find_uid(acl, uid, &entry);
+ if (r <= 0) {
+
+ if (acl_create_entry(&acl, &entry) < 0 ||
+ acl_set_tag_type(entry, ACL_USER) < 0 ||
+ acl_set_qualifier(entry, &uid) < 0) {
+ log_warning("Failed to patch ACL on %s, ignoring: %m", f->path);
+ goto finish;
+ }
+ }
+
+ if (acl_get_permset(entry, &permset) < 0 ||
+ acl_add_perm(permset, ACL_READ) < 0 ||
+ acl_calc_mask(&acl) < 0) {
+ log_warning("Failed to patch ACL on %s, ignoring: %m", f->path);
+ goto finish;
+ }
+
+ if (acl_set_fd(f->fd, acl) < 0)
+ log_warning("Failed to set ACL on %s, ignoring: %m", f->path);
+
+finish:
+ acl_free(acl);
+#endif
+}
+
+static JournalFile* find_journal(Server *s, uid_t uid) {
+ char *p;
+ int r;
+ JournalFile *f;
+ sd_id128_t machine;
+
+ assert(s);
+
+ /* We split up user logs only on /var, not on /run. If the
+ * runtime file is open, we write to it exclusively, in order
+ * to guarantee proper order as soon as we flush /run to
+ * /var and close the runtime file. */
+
+ if (s->runtime_journal)
+ return s->runtime_journal;
+
+ if (uid <= 0)
+ return s->system_journal;
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return s->system_journal;
+
+ f = hashmap_get(s->user_journals, UINT32_TO_PTR(uid));
+ if (f)
+ return f;
+
+ if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/user-%lu.journal",
+ SD_ID128_FORMAT_VAL(machine), (unsigned long) uid) < 0)
+ return s->system_journal;
+
+ while (hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) {
+ /* Too many open? Then let's close one */
+ f = hashmap_steal_first(s->user_journals);
+ assert(f);
+ journal_file_close(f);
+ }
+
+ r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->compress, s->seal, &s->system_metrics, s->mmap, s->system_journal, &f);
+ free(p);
+
+ if (r < 0)
+ return s->system_journal;
+
+ server_fix_perms(s, f, uid);
+
+ r = hashmap_put(s->user_journals, UINT32_TO_PTR(uid), f);
+ if (r < 0) {
+ journal_file_close(f);
+ return s->system_journal;
+ }
+
+ return f;
+}
+
+void server_rotate(Server *s) {
+ JournalFile *f;
+ void *k;
+ Iterator i;
+ int r;
+
+ log_debug("Rotating...");
+
+ if (s->runtime_journal) {
+ r = journal_file_rotate(&s->runtime_journal, s->compress, false);
+ if (r < 0)
+ if (s->runtime_journal)
+ log_error("Failed to rotate %s: %s", s->runtime_journal->path, strerror(-r));
+ else
+ log_error("Failed to create new runtime journal: %s", strerror(-r));
+ else
+ server_fix_perms(s, s->runtime_journal, 0);
+ }
+
+ if (s->system_journal) {
+ r = journal_file_rotate(&s->system_journal, s->compress, s->seal);
+ if (r < 0)
+ if (s->system_journal)
+ log_error("Failed to rotate %s: %s", s->system_journal->path, strerror(-r));
+ else
+ log_error("Failed to create new system journal: %s", strerror(-r));
+
+ else
+ server_fix_perms(s, s->system_journal, 0);
+ }
+
+ HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) {
+ r = journal_file_rotate(&f, s->compress, s->seal);
+ if (r < 0)
+ if (f->path)
+ log_error("Failed to rotate %s: %s", f->path, strerror(-r));
+ else
+ log_error("Failed to create user journal: %s", strerror(-r));
+ else {
+ hashmap_replace(s->user_journals, k, f);
+ server_fix_perms(s, f, PTR_TO_UINT32(k));
+ }
+ }
+}
+
+void server_vacuum(Server *s) {
+ char *p;
+ char ids[33];
+ sd_id128_t machine;
+ int r;
+
+ log_debug("Vacuuming...");
+
+ s->oldest_file_usec = 0;
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0) {
+ log_error("Failed to get machine ID: %s", strerror(-r));
+ return;
+ }
+
+ sd_id128_to_string(machine, ids);
+
+ if (s->system_journal) {
+ p = strappend("/var/log/journal/", ids);
+ if (!p) {
+ log_oom();
+ return;
+ }
+
+ r = journal_directory_vacuum(p, s->system_metrics.max_use, s->system_metrics.keep_free, s->max_retention_usec, &s->oldest_file_usec);
+ if (r < 0 && r != -ENOENT)
+ log_error("Failed to vacuum %s: %s", p, strerror(-r));
+ free(p);
+ }
+
+ if (s->runtime_journal) {
+ p = strappend("/run/log/journal/", ids);
+ if (!p) {
+ log_oom();
+ return;
+ }
+
+ r = journal_directory_vacuum(p, s->runtime_metrics.max_use, s->runtime_metrics.keep_free, s->max_retention_usec, &s->oldest_file_usec);
+ if (r < 0 && r != -ENOENT)
+ log_error("Failed to vacuum %s: %s", p, strerror(-r));
+ free(p);
+ }
+
+ s->cached_available_space_timestamp = 0;
+}
+
+static char *shortened_cgroup_path(pid_t pid) {
+ int r;
+ char *process_path, *init_path, *path;
+
+ assert(pid > 0);
+
+ r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &process_path);
+ if (r < 0)
+ return NULL;
+
+ r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, 1, &init_path);
+ if (r < 0) {
+ free(process_path);
+ return NULL;
+ }
+
+ if (endswith(init_path, "/system"))
+ init_path[strlen(init_path) - 7] = 0;
+ else if (streq(init_path, "/"))
+ init_path[0] = 0;
+
+ if (startswith(process_path, init_path)) {
+ char *p;
+
+ p = strdup(process_path + strlen(init_path));
+ if (!p) {
+ free(process_path);
+ free(init_path);
+ return NULL;
+ }
+ path = p;
+ } else {
+ path = process_path;
+ process_path = NULL;
+ }
+
+ free(process_path);
+ free(init_path);
+
+ return path;
+}
+
+bool shall_try_append_again(JournalFile *f, int r) {
+
+ /* -E2BIG Hit configured limit
+ -EFBIG Hit fs limit
+ -EDQUOT Quota limit hit
+ -ENOSPC Disk full
+ -EHOSTDOWN Other machine
+ -EBUSY Unclean shutdown
+ -EPROTONOSUPPORT Unsupported feature
+ -EBADMSG Corrupted
+ -ENODATA Truncated
+ -ESHUTDOWN Already archived */
+
+ if (r == -E2BIG || r == -EFBIG || r == -EDQUOT || r == -ENOSPC)
+ log_debug("%s: Allocation limit reached, rotating.", f->path);
+ else if (r == -EHOSTDOWN)
+ log_info("%s: Journal file from other machine, rotating.", f->path);
+ else if (r == -EBUSY)
+ log_info("%s: Unclean shutdown, rotating.", f->path);
+ else if (r == -EPROTONOSUPPORT)
+ log_info("%s: Unsupported feature, rotating.", f->path);
+ else if (r == -EBADMSG || r == -ENODATA || r == ESHUTDOWN)
+ log_warning("%s: Journal file corrupted, rotating.", f->path);
+ else
+ return false;
+
+ return true;
+}
+
+static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned n) {
+ JournalFile *f;
+ bool vacuumed = false;
+ int r;
+
+ assert(s);
+ assert(iovec);
+ assert(n > 0);
+
+ f = find_journal(s, uid);
+ if (!f)
+ return;
+
+ if (journal_file_rotate_suggested(f, s->max_file_usec)) {
+ log_debug("%s: Journal header limits reached or header out-of-date, rotating.", f->path);
+ server_rotate(s);
+ server_vacuum(s);
+ vacuumed = true;
+
+ f = find_journal(s, uid);
+ if (!f)
+ return;
+ }
+
+ r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL);
+ if (r >= 0)
+ return;
+
+ if (vacuumed || !shall_try_append_again(f, r)) {
+ log_error("Failed to write entry, ignoring: %s", strerror(-r));
+ return;
+ }
+
+ server_rotate(s);
+ server_vacuum(s);
+
+ f = find_journal(s, uid);
+ if (!f)
+ return;
+
+ log_debug("Retrying write.");
+ r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL);
+ if (r < 0)
+ log_error("Failed to write entry, ignoring: %s", strerror(-r));
+}
+
+static void dispatch_message_real(
+ Server *s,
+ struct iovec *iovec, unsigned n, unsigned m,
+ struct ucred *ucred,
+ struct timeval *tv,
+ const char *label, size_t label_len,
+ const char *unit_id) {
+
+ char *pid = NULL, *uid = NULL, *gid = NULL,
+ *source_time = NULL, *boot_id = NULL, *machine_id = NULL,
+ *comm = NULL, *cmdline = NULL, *hostname = NULL,
+ *audit_session = NULL, *audit_loginuid = NULL,
+ *exe = NULL, *cgroup = NULL, *session = NULL,
+ *owner_uid = NULL, *unit = NULL, *selinux_context = NULL;
+
+ char idbuf[33];
+ sd_id128_t id;
+ int r;
+ char *t;
+ uid_t loginuid = 0, realuid = 0;
+
+ assert(s);
+ assert(iovec);
+ assert(n > 0);
+ assert(n + N_IOVEC_META_FIELDS <= m);
+
+ if (ucred) {
+ uint32_t audit;
+#ifdef HAVE_LOGIND
+ uid_t owner;
+#endif
+
+ realuid = ucred->uid;
+
+ if (asprintf(&pid, "_PID=%lu", (unsigned long) ucred->pid) >= 0)
+ IOVEC_SET_STRING(iovec[n++], pid);
+
+ if (asprintf(&uid, "_UID=%lu", (unsigned long) ucred->uid) >= 0)
+ IOVEC_SET_STRING(iovec[n++], uid);
+
+ if (asprintf(&gid, "_GID=%lu", (unsigned long) ucred->gid) >= 0)
+ IOVEC_SET_STRING(iovec[n++], gid);
+
+ r = get_process_comm(ucred->pid, &t);
+ if (r >= 0) {
+ comm = strappend("_COMM=", t);
+ free(t);
+
+ if (comm)
+ IOVEC_SET_STRING(iovec[n++], comm);
+ }
+
+ r = get_process_exe(ucred->pid, &t);
+ if (r >= 0) {
+ exe = strappend("_EXE=", t);
+ free(t);
+
+ if (exe)
+ IOVEC_SET_STRING(iovec[n++], exe);
+ }
+
+ r = get_process_cmdline(ucred->pid, LINE_MAX, false, &t);
+ if (r >= 0) {
+ cmdline = strappend("_CMDLINE=", t);
+ free(t);
+
+ if (cmdline)
+ IOVEC_SET_STRING(iovec[n++], cmdline);
+ }
+
+ r = audit_session_from_pid(ucred->pid, &audit);
+ if (r >= 0)
+ if (asprintf(&audit_session, "_AUDIT_SESSION=%lu", (unsigned long) audit) >= 0)
+ IOVEC_SET_STRING(iovec[n++], audit_session);
+
+ r = audit_loginuid_from_pid(ucred->pid, &loginuid);
+ if (r >= 0)
+ if (asprintf(&audit_loginuid, "_AUDIT_LOGINUID=%lu", (unsigned long) loginuid) >= 0)
+ IOVEC_SET_STRING(iovec[n++], audit_loginuid);
+
+ t = shortened_cgroup_path(ucred->pid);
+ if (t) {
+ cgroup = strappend("_SYSTEMD_CGROUP=", t);
+ free(t);
+
+ if (cgroup)
+ IOVEC_SET_STRING(iovec[n++], cgroup);
+ }
+
+#ifdef HAVE_LOGIND
+ if (sd_pid_get_session(ucred->pid, &t) >= 0) {
+ session = strappend("_SYSTEMD_SESSION=", t);
+ free(t);
+
+ if (session)
+ IOVEC_SET_STRING(iovec[n++], session);
+ }
+
+ if (sd_pid_get_owner_uid(ucred->uid, &owner) >= 0)
+ if (asprintf(&owner_uid, "_SYSTEMD_OWNER_UID=%lu", (unsigned long) owner) >= 0)
+ IOVEC_SET_STRING(iovec[n++], owner_uid);
+#endif
+
+ if (cg_pid_get_unit(ucred->pid, &t) >= 0) {
+ unit = strappend("_SYSTEMD_UNIT=", t);
+ free(t);
+ } else if (unit_id)
+ unit = strappend("_SYSTEMD_UNIT=", unit_id);
+
+ if (unit)
+ IOVEC_SET_STRING(iovec[n++], unit);
+
+#ifdef HAVE_SELINUX
+ if (label) {
+ selinux_context = malloc(sizeof("_SELINUX_CONTEXT=") + label_len);
+ if (selinux_context) {
+ memcpy(selinux_context, "_SELINUX_CONTEXT=", sizeof("_SELINUX_CONTEXT=")-1);
+ memcpy(selinux_context+sizeof("_SELINUX_CONTEXT=")-1, label, label_len);
+ selinux_context[sizeof("_SELINUX_CONTEXT=")-1+label_len] = 0;
+ IOVEC_SET_STRING(iovec[n++], selinux_context);
+ }
+ } else {
+ security_context_t con;
+
+ if (getpidcon(ucred->pid, &con) >= 0) {
+ selinux_context = strappend("_SELINUX_CONTEXT=", con);
+ if (selinux_context)
+ IOVEC_SET_STRING(iovec[n++], selinux_context);
+
+ freecon(con);
+ }
+ }
+#endif
+ }
+
+ if (tv) {
+ if (asprintf(&source_time, "_SOURCE_REALTIME_TIMESTAMP=%llu",
+ (unsigned long long) timeval_load(tv)) >= 0)
+ IOVEC_SET_STRING(iovec[n++], source_time);
+ }
+
+ /* Note that strictly speaking storing the boot id here is
+ * redundant since the entry includes this in-line
+ * anyway. However, we need this indexed, too. */
+ r = sd_id128_get_boot(&id);
+ if (r >= 0)
+ if (asprintf(&boot_id, "_BOOT_ID=%s", sd_id128_to_string(id, idbuf)) >= 0)
+ IOVEC_SET_STRING(iovec[n++], boot_id);
+
+ r = sd_id128_get_machine(&id);
+ if (r >= 0)
+ if (asprintf(&machine_id, "_MACHINE_ID=%s", sd_id128_to_string(id, idbuf)) >= 0)
+ IOVEC_SET_STRING(iovec[n++], machine_id);
+
+ t = gethostname_malloc();
+ if (t) {
+ hostname = strappend("_HOSTNAME=", t);
+ free(t);
+ if (hostname)
+ IOVEC_SET_STRING(iovec[n++], hostname);
+ }
+
+ assert(n <= m);
+
+ write_to_journal(s,
+ s->split_mode == SPLIT_NONE ? 0 :
+ (s->split_mode == SPLIT_UID ? realuid :
+ (realuid == 0 ? 0 : loginuid)), iovec, n);
+
+ free(pid);
+ free(uid);
+ free(gid);
+ free(comm);
+ free(exe);
+ free(cmdline);
+ free(source_time);
+ free(boot_id);
+ free(machine_id);
+ free(hostname);
+ free(audit_session);
+ free(audit_loginuid);
+ free(cgroup);
+ free(session);
+ free(owner_uid);
+ free(unit);
+ free(selinux_context);
+}
+
+void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) {
+ char mid[11 + 32 + 1];
+ char buffer[16 + LINE_MAX + 1];
+ struct iovec iovec[N_IOVEC_META_FIELDS + 4];
+ int n = 0;
+ va_list ap;
+ struct ucred ucred;
+
+ assert(s);
+ assert(format);
+
+ IOVEC_SET_STRING(iovec[n++], "PRIORITY=6");
+ IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=driver");
+
+ memcpy(buffer, "MESSAGE=", 8);
+ va_start(ap, format);
+ vsnprintf(buffer + 8, sizeof(buffer) - 8, format, ap);
+ va_end(ap);
+ char_array_0(buffer);
+ IOVEC_SET_STRING(iovec[n++], buffer);
+
+ if (!sd_id128_equal(message_id, SD_ID128_NULL)) {
+ snprintf(mid, sizeof(mid), MESSAGE_ID(message_id));
+ char_array_0(mid);
+ IOVEC_SET_STRING(iovec[n++], mid);
+ }
+
+ zero(ucred);
+ ucred.pid = getpid();
+ ucred.uid = getuid();
+ ucred.gid = getgid();
+
+ dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL);
+}
+
+void server_dispatch_message(
+ Server *s,
+ struct iovec *iovec, unsigned n, unsigned m,
+ struct ucred *ucred,
+ struct timeval *tv,
+ const char *label, size_t label_len,
+ const char *unit_id,
+ int priority) {
+
+ int rl;
+ char *path = NULL, *c;
+
+ assert(s);
+ assert(iovec || n == 0);
+
+ if (n == 0)
+ return;
+
+ if (LOG_PRI(priority) > s->max_level_store)
+ return;
+
+ if (!ucred)
+ goto finish;
+
+ path = shortened_cgroup_path(ucred->pid);
+ if (!path)
+ goto finish;
+
+ /* example: /user/lennart/3/foobar
+ * /system/dbus.service/foobar
+ *
+ * So let's cut of everything past the third /, since that is
+ * where user directories start */
+
+ c = strchr(path, '/');
+ if (c) {
+ c = strchr(c+1, '/');
+ if (c) {
+ c = strchr(c+1, '/');
+ if (c)
+ *c = 0;
+ }
+ }
+
+ rl = journal_rate_limit_test(s->rate_limit, path, priority & LOG_PRIMASK, available_space(s));
+
+ if (rl == 0) {
+ free(path);
+ return;
+ }
+
+ /* Write a suppression message if we suppressed something */
+ if (rl > 1)
+ server_driver_message(s, SD_MESSAGE_JOURNAL_DROPPED, "Suppressed %u messages from %s", rl - 1, path);
+
+ free(path);
+
+finish:
+ dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id);
+}
+
+
+static int system_journal_open(Server *s) {
+ int r;
+ char *fn;
+ sd_id128_t machine;
+ char ids[33];
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return r;
+
+ sd_id128_to_string(machine, ids);
+
+ if (!s->system_journal &&
+ (s->storage == STORAGE_PERSISTENT || s->storage == STORAGE_AUTO) &&
+ access("/run/systemd/journal/flushed", F_OK) >= 0) {
+
+ /* If in auto mode: first try to create the machine
+ * path, but not the prefix.
+ *
+ * If in persistent mode: create /var/log/journal and
+ * the machine path */
+
+ if (s->storage == STORAGE_PERSISTENT)
+ (void) mkdir("/var/log/journal/", 0755);
+
+ fn = strappend("/var/log/journal/", ids);
+ if (!fn)
+ return -ENOMEM;
+
+ (void) mkdir(fn, 0755);
+ free(fn);
+
+ fn = strjoin("/var/log/journal/", ids, "/system.journal", NULL);
+ if (!fn)
+ return -ENOMEM;
+
+ r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, s->seal, &s->system_metrics, s->mmap, NULL, &s->system_journal);
+ free(fn);
+
+ if (r >= 0) {
+ char fb[FORMAT_BYTES_MAX];
+
+ server_fix_perms(s, s->system_journal, 0);
+ server_driver_message(s, SD_ID128_NULL, "Allowing system journal files to grow to %s.",
+ format_bytes(fb, sizeof(fb), s->system_metrics.max_use));
+
+ } else if (r < 0) {
+
+ if (r != -ENOENT && r != -EROFS)
+ log_warning("Failed to open system journal: %s", strerror(-r));
+
+ r = 0;
+ }
+ }
+
+ if (!s->runtime_journal &&
+ (s->storage != STORAGE_NONE)) {
+
+ fn = strjoin("/run/log/journal/", ids, "/system.journal", NULL);
+ if (!fn)
+ return -ENOMEM;
+
+ if (s->system_journal) {
+
+ /* Try to open the runtime journal, but only
+ * if it already exists, so that we can flush
+ * it into the system journal */
+
+ r = journal_file_open(fn, O_RDWR, 0640, s->compress, false, &s->runtime_metrics, s->mmap, NULL, &s->runtime_journal);
+ free(fn);
+
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning("Failed to open runtime journal: %s", strerror(-r));
+
+ r = 0;
+ }
+
+ } else {
+
+ /* OK, we really need the runtime journal, so create
+ * it if necessary. */
+
+ (void) mkdir_parents(fn, 0755);
+ r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, false, &s->runtime_metrics, s->mmap, NULL, &s->runtime_journal);
+ free(fn);
+
+ if (r < 0) {
+ log_error("Failed to open runtime journal: %s", strerror(-r));
+ return r;
+ }
+ }
+
+ if (s->runtime_journal) {
+ char fb[FORMAT_BYTES_MAX];
+
+ server_fix_perms(s, s->runtime_journal, 0);
+ server_driver_message(s, SD_ID128_NULL, "Allowing runtime journal files to grow to %s.",
+ format_bytes(fb, sizeof(fb), s->runtime_metrics.max_use));
+ }
+ }
+
+ return r;
+}
+
+int server_flush_to_var(Server *s) {
+ int r;
+ sd_id128_t machine;
+ sd_journal *j = NULL;
+
+ assert(s);
+
+ if (s->storage != STORAGE_AUTO &&
+ s->storage != STORAGE_PERSISTENT)
+ return 0;
+
+ if (!s->runtime_journal)
+ return 0;
+
+ system_journal_open(s);
+
+ if (!s->system_journal)
+ return 0;
+
+ log_debug("Flushing to /var...");
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0) {
+ log_error("Failed to get machine id: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY);
+ if (r < 0) {
+ log_error("Failed to read runtime journal: %s", strerror(-r));
+ return r;
+ }
+
+ SD_JOURNAL_FOREACH(j) {
+ Object *o = NULL;
+ JournalFile *f;
+
+ f = j->current_file;
+ assert(f && f->current_offset > 0);
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+ if (r < 0) {
+ log_error("Can't read entry: %s", strerror(-r));
+ goto finish;
+ }
+
+ r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL);
+ if (r >= 0)
+ continue;
+
+ if (!shall_try_append_again(s->system_journal, r)) {
+ log_error("Can't write entry: %s", strerror(-r));
+ goto finish;
+ }
+
+ server_rotate(s);
+ server_vacuum(s);
+
+ log_debug("Retrying write.");
+ r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL);
+ if (r < 0) {
+ log_error("Can't write entry: %s", strerror(-r));
+ goto finish;
+ }
+ }
+
+finish:
+ journal_file_post_change(s->system_journal);
+
+ journal_file_close(s->runtime_journal);
+ s->runtime_journal = NULL;
+
+ if (r >= 0)
+ rm_rf("/run/log/journal", false, true, false);
+
+ if (j)
+ sd_journal_close(j);
+
+ return r;
+}
+
+int process_event(Server *s, struct epoll_event *ev) {
+ assert(s);
+ assert(ev);
+
+ if (ev->data.fd == s->signal_fd) {
+ struct signalfd_siginfo sfsi;
+ ssize_t n;
+
+ if (ev->events != EPOLLIN) {
+ log_error("Got invalid event from epoll.");
+ return -EIO;
+ }
+
+ n = read(s->signal_fd, &sfsi, sizeof(sfsi));
+ if (n != sizeof(sfsi)) {
+
+ if (n >= 0)
+ return -EIO;
+
+ if (errno == EINTR || errno == EAGAIN)
+ return 1;
+
+ return -errno;
+ }
+
+ log_info("Received SIG%s", signal_to_string(sfsi.ssi_signo));
+
+ if (sfsi.ssi_signo == SIGUSR1) {
+ touch("/run/systemd/journal/flushed");
+ server_flush_to_var(s);
+ return 1;
+ }
+
+ if (sfsi.ssi_signo == SIGUSR2) {
+ server_rotate(s);
+ server_vacuum(s);
+ return 1;
+ }
+
+ return 0;
+
+ } else if (ev->data.fd == s->dev_kmsg_fd) {
+ int r;
+
+ if (ev->events != EPOLLIN) {
+ log_error("Got invalid event from epoll.");
+ return -EIO;
+ }
+
+ r = server_read_dev_kmsg(s);
+ if (r < 0)
+ return r;
+
+ return 1;
+
+ } else if (ev->data.fd == s->native_fd ||
+ ev->data.fd == s->syslog_fd) {
+
+ if (ev->events != EPOLLIN) {
+ log_error("Got invalid event from epoll.");
+ return -EIO;
+ }
+
+ for (;;) {
+ struct msghdr msghdr;
+ struct iovec iovec;
+ struct ucred *ucred = NULL;
+ struct timeval *tv = NULL;
+ struct cmsghdr *cmsg;
+ char *label = NULL;
+ size_t label_len = 0;
+ union {
+ struct cmsghdr cmsghdr;
+
+ /* We use NAME_MAX space for the
+ * SELinux label here. The kernel
+ * currently enforces no limit, but
+ * according to suggestions from the
+ * SELinux people this will change and
+ * it will probably be identical to
+ * NAME_MAX. For now we use that, but
+ * this should be updated one day when
+ * the final limit is known.*/
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
+ CMSG_SPACE(sizeof(struct timeval)) +
+ CMSG_SPACE(sizeof(int)) + /* fd */
+ CMSG_SPACE(NAME_MAX)]; /* selinux label */
+ } control;
+ ssize_t n;
+ int v;
+ int *fds = NULL;
+ unsigned n_fds = 0;
+
+ if (ioctl(ev->data.fd, SIOCINQ, &v) < 0) {
+ log_error("SIOCINQ failed: %m");
+ return -errno;
+ }
+
+ if (s->buffer_size < (size_t) v) {
+ void *b;
+ size_t l;
+
+ l = MAX(LINE_MAX + (size_t) v, s->buffer_size * 2);
+ b = realloc(s->buffer, l+1);
+
+ if (!b) {
+ log_error("Couldn't increase buffer.");
+ return -ENOMEM;
+ }
+
+ s->buffer_size = l;
+ s->buffer = b;
+ }
+
+ zero(iovec);
+ iovec.iov_base = s->buffer;
+ iovec.iov_len = s->buffer_size;
+
+ zero(control);
+ zero(msghdr);
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_control = &control;
+ msghdr.msg_controllen = sizeof(control);
+
+ n = recvmsg(ev->data.fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ if (n < 0) {
+
+ if (errno == EINTR || errno == EAGAIN)
+ return 1;
+
+ log_error("recvmsg() failed: %m");
+ return -errno;
+ }
+
+ for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg; cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
+
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_CREDENTIALS &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred)))
+ ucred = (struct ucred*) CMSG_DATA(cmsg);
+ else if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_SECURITY) {
+ label = (char*) CMSG_DATA(cmsg);
+ label_len = cmsg->cmsg_len - CMSG_LEN(0);
+ } else if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SO_TIMESTAMP &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
+ tv = (struct timeval*) CMSG_DATA(cmsg);
+ else if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
+ fds = (int*) CMSG_DATA(cmsg);
+ n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ }
+ }
+
+ if (ev->data.fd == s->syslog_fd) {
+ char *e;
+
+ if (n > 0 && n_fds == 0) {
+ e = memchr(s->buffer, '\n', n);
+ if (e)
+ *e = 0;
+ else
+ s->buffer[n] = 0;
+
+ server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len);
+ } else if (n_fds > 0)
+ log_warning("Got file descriptors via syslog socket. Ignoring.");
+
+ } else {
+ if (n > 0 && n_fds == 0)
+ server_process_native_message(s, s->buffer, n, ucred, tv, label, label_len);
+ else if (n == 0 && n_fds == 1)
+ server_process_native_file(s, fds[0], ucred, tv, label, label_len);
+ else if (n_fds > 0)
+ log_warning("Got too many file descriptors via native socket. Ignoring.");
+ }
+
+ close_many(fds, n_fds);
+ }
+
+ return 1;
+
+ } else if (ev->data.fd == s->stdout_fd) {
+
+ if (ev->events != EPOLLIN) {
+ log_error("Got invalid event from epoll.");
+ return -EIO;
+ }
+
+ stdout_stream_new(s);
+ return 1;
+
+ } else {
+ StdoutStream *stream;
+
+ if ((ev->events|EPOLLIN|EPOLLHUP) != (EPOLLIN|EPOLLHUP)) {
+ log_error("Got invalid event from epoll.");
+ return -EIO;
+ }
+
+ /* If it is none of the well-known fds, it must be an
+ * stdout stream fd. Note that this is a bit ugly here
+ * (since we rely that none of the well-known fds
+ * could be interpreted as pointer), but nonetheless
+ * safe, since the well-known fds would never get an
+ * fd > 4096, i.e. beyond the first memory page */
+
+ stream = ev->data.ptr;
+
+ if (stdout_stream_process(stream) <= 0)
+ stdout_stream_free(stream);
+
+ return 1;
+ }
+
+ log_error("Unknown event.");
+ return 0;
+}
+
+static int open_signalfd(Server *s) {
+ sigset_t mask;
+ struct epoll_event ev;
+
+ assert(s);
+
+ assert_se(sigemptyset(&mask) == 0);
+ sigset_add_many(&mask, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, -1);
+ assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+ s->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
+ if (s->signal_fd < 0) {
+ log_error("signalfd(): %m");
+ return -errno;
+ }
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.fd = s->signal_fd;
+
+ if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->signal_fd, &ev) < 0) {
+ log_error("epoll_ctl(): %m");
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int server_parse_proc_cmdline(Server *s) {
+ char *line, *w, *state;
+ int r;
+ size_t l;
+
+ if (detect_container(NULL) > 0)
+ return 0;
+
+ r = read_one_line_file("/proc/cmdline", &line);
+ if (r < 0) {
+ log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
+ return 0;
+ }
+
+ FOREACH_WORD_QUOTED(w, l, line, state) {
+ char *word;
+
+ word = strndup(w, l);
+ if (!word) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (startswith(word, "systemd.journald.forward_to_syslog=")) {
+ r = parse_boolean(word + 35);
+ if (r < 0)
+ log_warning("Failed to parse forward to syslog switch %s. Ignoring.", word + 35);
+ else
+ s->forward_to_syslog = r;
+ } else if (startswith(word, "systemd.journald.forward_to_kmsg=")) {
+ r = parse_boolean(word + 33);
+ if (r < 0)
+ log_warning("Failed to parse forward to kmsg switch %s. Ignoring.", word + 33);
+ else
+ s->forward_to_kmsg = r;
+ } else if (startswith(word, "systemd.journald.forward_to_console=")) {
+ r = parse_boolean(word + 36);
+ if (r < 0)
+ log_warning("Failed to parse forward to console switch %s. Ignoring.", word + 36);
+ else
+ s->forward_to_console = r;
+ } else if (startswith(word, "systemd.journald"))
+ log_warning("Invalid systemd.journald parameter. Ignoring.");
+
+ free(word);
+ }
+
+ r = 0;
+
+finish:
+ free(line);
+ return r;
+}
+
+static int server_parse_config_file(Server *s) {
+ FILE *f;
+ const char *fn;
+ int r;
+
+ assert(s);
+
+ fn = "/etc/systemd/journald.conf";
+ f = fopen(fn, "re");
+ if (!f) {
+ if (errno == ENOENT)
+ return 0;
+
+ log_warning("Failed to open configuration file %s: %m", fn);
+ return -errno;
+ }
+
+ r = config_parse(fn, f, "Journal\0", config_item_perf_lookup, (void*) journald_gperf_lookup, false, s);
+ if (r < 0)
+ log_warning("Failed to parse configuration file: %s", strerror(-r));
+
+ fclose(f);
+
+ return r;
+}
+
+int server_init(Server *s) {
+ int n, r, fd;
+
+ assert(s);
+
+ zero(*s);
+ s->syslog_fd = s->native_fd = s->stdout_fd = s->signal_fd = s->epoll_fd = s->dev_kmsg_fd = -1;
+ s->compress = true;
+ s->seal = true;
+
+ s->rate_limit_interval = DEFAULT_RATE_LIMIT_INTERVAL;
+ s->rate_limit_burst = DEFAULT_RATE_LIMIT_BURST;
+
+ s->forward_to_syslog = true;
+
+ s->max_level_store = LOG_DEBUG;
+ s->max_level_syslog = LOG_DEBUG;
+ s->max_level_kmsg = LOG_NOTICE;
+ s->max_level_console = LOG_INFO;
+
+ memset(&s->system_metrics, 0xFF, sizeof(s->system_metrics));
+ memset(&s->runtime_metrics, 0xFF, sizeof(s->runtime_metrics));
+
+ server_parse_config_file(s);
+ server_parse_proc_cmdline(s);
+
+ mkdir_p("/run/systemd/journal", 0755);
+
+ s->user_journals = hashmap_new(trivial_hash_func, trivial_compare_func);
+ if (!s->user_journals)
+ return log_oom();
+
+ s->mmap = mmap_cache_new();
+ if (!s->mmap)
+ return log_oom();
+
+ s->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+ if (s->epoll_fd < 0) {
+ log_error("Failed to create epoll object: %m");
+ return -errno;
+ }
+
+ n = sd_listen_fds(true);
+ if (n < 0) {
+ log_error("Failed to read listening file descriptors from environment: %s", strerror(-n));
+ return n;
+ }
+
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
+
+ if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/socket", 0) > 0) {
+
+ if (s->native_fd >= 0) {
+ log_error("Too many native sockets passed.");
+ return -EINVAL;
+ }
+
+ s->native_fd = fd;
+
+ } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/journal/stdout", 0) > 0) {
+
+ if (s->stdout_fd >= 0) {
+ log_error("Too many stdout sockets passed.");
+ return -EINVAL;
+ }
+
+ s->stdout_fd = fd;
+
+ } else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/dev/log", 0) > 0) {
+
+ if (s->syslog_fd >= 0) {
+ log_error("Too many /dev/log sockets passed.");
+ return -EINVAL;
+ }
+
+ s->syslog_fd = fd;
+
+ } else {
+ log_error("Unknown socket passed.");
+ return -EINVAL;
+ }
+ }
+
+ r = server_open_syslog_socket(s);
+ if (r < 0)
+ return r;
+
+ r = server_open_native_socket(s);
+ if (r < 0)
+ return r;
+
+ r = server_open_stdout_socket(s);
+ if (r < 0)
+ return r;
+
+ r = server_open_dev_kmsg(s);
+ if (r < 0)
+ return r;
+
+ r = server_open_kernel_seqnum(s);
+ if (r < 0)
+ return r;
+
+ r = open_signalfd(s);
+ if (r < 0)
+ return r;
+
+ s->udev = udev_new();
+ if (!s->udev)
+ return -ENOMEM;
+
+ s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst);
+ if (!s->rate_limit)
+ return -ENOMEM;
+
+ r = system_journal_open(s);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void server_maybe_append_tags(Server *s) {
+#ifdef HAVE_GCRYPT
+ JournalFile *f;
+ Iterator i;
+ usec_t n;
+
+ n = now(CLOCK_REALTIME);
+
+ if (s->system_journal)
+ journal_file_maybe_append_tag(s->system_journal, n);
+
+ HASHMAP_FOREACH(f, s->user_journals, i)
+ journal_file_maybe_append_tag(f, n);
+#endif
+}
+
+void server_done(Server *s) {
+ JournalFile *f;
+ assert(s);
+
+ while (s->stdout_streams)
+ stdout_stream_free(s->stdout_streams);
+
+ if (s->system_journal)
+ journal_file_close(s->system_journal);
+
+ if (s->runtime_journal)
+ journal_file_close(s->runtime_journal);
+
+ while ((f = hashmap_steal_first(s->user_journals)))
+ journal_file_close(f);
+
+ hashmap_free(s->user_journals);
+
+ if (s->epoll_fd >= 0)
+ close_nointr_nofail(s->epoll_fd);
+
+ if (s->signal_fd >= 0)
+ close_nointr_nofail(s->signal_fd);
+
+ if (s->syslog_fd >= 0)
+ close_nointr_nofail(s->syslog_fd);
+
+ if (s->native_fd >= 0)
+ close_nointr_nofail(s->native_fd);
+
+ if (s->stdout_fd >= 0)
+ close_nointr_nofail(s->stdout_fd);
+
+ if (s->dev_kmsg_fd >= 0)
+ close_nointr_nofail(s->dev_kmsg_fd);
+
+ if (s->rate_limit)
+ journal_rate_limit_free(s->rate_limit);
+
+ if (s->kernel_seqnum)
+ munmap(s->kernel_seqnum, sizeof(uint64_t));
+
+ free(s->buffer);
+ free(s->tty_path);
+
+ if (s->mmap)
+ mmap_cache_unref(s->mmap);
+
+ if (s->udev)
+ udev_unref(s->udev);
+}
diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h
new file mode 100644
index 0000000000..9f50a29e50
--- /dev/null
+++ b/src/journal/journald-server.h
@@ -0,0 +1,152 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/epoll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "journal-file.h"
+#include "hashmap.h"
+#include "util.h"
+#include "audit.h"
+#include "journald-rate-limit.h"
+#include "list.h"
+
+typedef enum Storage {
+ STORAGE_AUTO,
+ STORAGE_VOLATILE,
+ STORAGE_PERSISTENT,
+ STORAGE_NONE,
+ _STORAGE_MAX,
+ _STORAGE_INVALID = -1
+} Storage;
+
+typedef enum SplitMode {
+ SPLIT_LOGIN,
+ SPLIT_UID,
+ SPLIT_NONE,
+ _SPLIT_MAX,
+ _SPLIT_INVALID = -1
+} SplitMode;
+
+typedef struct StdoutStream StdoutStream;
+
+typedef struct Server {
+ int epoll_fd;
+ int signal_fd;
+ int syslog_fd;
+ int native_fd;
+ int stdout_fd;
+ int dev_kmsg_fd;
+
+ JournalFile *runtime_journal;
+ JournalFile *system_journal;
+ Hashmap *user_journals;
+
+ uint64_t seqnum;
+
+ char *buffer;
+ size_t buffer_size;
+
+ JournalRateLimit *rate_limit;
+ usec_t rate_limit_interval;
+ unsigned rate_limit_burst;
+
+ JournalMetrics runtime_metrics;
+ JournalMetrics system_metrics;
+
+ bool compress;
+ bool seal;
+
+ bool forward_to_kmsg;
+ bool forward_to_syslog;
+ bool forward_to_console;
+
+ unsigned n_forward_syslog_missed;
+ usec_t last_warn_forward_syslog_missed;
+
+ uint64_t cached_available_space;
+ usec_t cached_available_space_timestamp;
+
+ uint64_t var_available_timestamp;
+
+ usec_t max_retention_usec;
+ usec_t max_file_usec;
+ usec_t oldest_file_usec;
+
+ gid_t file_gid;
+ bool file_gid_valid;
+
+ LIST_HEAD(StdoutStream, stdout_streams);
+ unsigned n_stdout_streams;
+
+ char *tty_path;
+
+ int max_level_store;
+ int max_level_syslog;
+ int max_level_kmsg;
+ int max_level_console;
+
+ Storage storage;
+ SplitMode split_mode;
+
+ MMapCache *mmap;
+
+ bool dev_kmsg_readable;
+
+ uint64_t *kernel_seqnum;
+
+ struct udev *udev;
+} Server;
+
+#define N_IOVEC_META_FIELDS 17
+#define N_IOVEC_KERNEL_FIELDS 64
+#define N_IOVEC_UDEV_FIELDS 32
+
+void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority);
+void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...);
+
+/* gperf lookup function */
+const struct ConfigPerfItem* journald_gperf_lookup(const char *key, unsigned length);
+
+int config_parse_storage(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+const char *storage_to_string(Storage s);
+Storage storage_from_string(const char *s);
+
+int config_parse_split_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+const char *split_mode_to_string(SplitMode s);
+SplitMode split_mode_from_string(const char *s);
+
+void server_fix_perms(Server *s, JournalFile *f, uid_t uid);
+bool shall_try_append_again(JournalFile *f, int r);
+int server_init(Server *s);
+void server_done(Server *s);
+void server_vacuum(Server *s);
+void server_rotate(Server *s);
+int server_flush_to_var(Server *s);
+int process_event(Server *s, struct epoll_event *ev);
+void server_maybe_append_tags(Server *s);
diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c
new file mode 100644
index 0000000000..7b88f747db
--- /dev/null
+++ b/src/journal/journald-stream.c
@@ -0,0 +1,459 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <sys/epoll.h>
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#include "socket-util.h"
+#include "journald-server.h"
+#include "journald-stream.h"
+#include "journald-syslog.h"
+#include "journald-kmsg.h"
+#include "journald-console.h"
+
+#define STDOUT_STREAMS_MAX 4096
+
+typedef enum StdoutStreamState {
+ STDOUT_STREAM_IDENTIFIER,
+ STDOUT_STREAM_UNIT_ID,
+ STDOUT_STREAM_PRIORITY,
+ STDOUT_STREAM_LEVEL_PREFIX,
+ STDOUT_STREAM_FORWARD_TO_SYSLOG,
+ STDOUT_STREAM_FORWARD_TO_KMSG,
+ STDOUT_STREAM_FORWARD_TO_CONSOLE,
+ STDOUT_STREAM_RUNNING
+} StdoutStreamState;
+
+struct StdoutStream {
+ Server *server;
+ StdoutStreamState state;
+
+ int fd;
+
+ struct ucred ucred;
+#ifdef HAVE_SELINUX
+ security_context_t security_context;
+#endif
+
+ char *identifier;
+ char *unit_id;
+ int priority;
+ bool level_prefix:1;
+ bool forward_to_syslog:1;
+ bool forward_to_kmsg:1;
+ bool forward_to_console:1;
+
+ char buffer[LINE_MAX+1];
+ size_t length;
+
+ LIST_FIELDS(StdoutStream, stdout_stream);
+};
+
+static int stdout_stream_log(StdoutStream *s, const char *p) {
+ struct iovec iovec[N_IOVEC_META_FIELDS + 5];
+ char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_identifier = NULL;
+ unsigned n = 0;
+ int priority;
+ char *label = NULL;
+ size_t label_len = 0;
+
+ assert(s);
+ assert(p);
+
+ if (isempty(p))
+ return 0;
+
+ priority = s->priority;
+
+ if (s->level_prefix)
+ syslog_parse_priority((char**) &p, &priority);
+
+ if (s->forward_to_syslog || s->server->forward_to_syslog)
+ server_forward_syslog(s->server, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL);
+
+ if (s->forward_to_kmsg || s->server->forward_to_kmsg)
+ server_forward_kmsg(s->server, priority, s->identifier, p, &s->ucred);
+
+ if (s->forward_to_console || s->server->forward_to_console)
+ server_forward_console(s->server, priority, s->identifier, p, &s->ucred);
+
+ IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout");
+
+ if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0)
+ IOVEC_SET_STRING(iovec[n++], syslog_priority);
+
+ if (priority & LOG_FACMASK)
+ if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0)
+ IOVEC_SET_STRING(iovec[n++], syslog_facility);
+
+ if (s->identifier) {
+ syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier);
+ if (syslog_identifier)
+ IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+ }
+
+ message = strappend("MESSAGE=", p);
+ if (message)
+ IOVEC_SET_STRING(iovec[n++], message);
+
+#ifdef HAVE_SELINUX
+ if (s->security_context) {
+ label = (char*) s->security_context;
+ label_len = strlen((char*) s->security_context);
+ }
+#endif
+
+ server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, label, label_len, s->unit_id, priority);
+
+ free(message);
+ free(syslog_priority);
+ free(syslog_facility);
+ free(syslog_identifier);
+
+ return 0;
+}
+
+static int stdout_stream_line(StdoutStream *s, char *p) {
+ int r;
+
+ assert(s);
+ assert(p);
+
+ p = strstrip(p);
+
+ switch (s->state) {
+
+ case STDOUT_STREAM_IDENTIFIER:
+ if (isempty(p))
+ s->identifier = NULL;
+ else {
+ s->identifier = strdup(p);
+ if (!s->identifier)
+ return log_oom();
+ }
+
+ s->state = STDOUT_STREAM_UNIT_ID;
+ return 0;
+
+ case STDOUT_STREAM_UNIT_ID:
+ if (s->ucred.uid == 0) {
+ if (isempty(p))
+ s->unit_id = NULL;
+ else {
+ s->unit_id = strdup(p);
+ if (!s->unit_id)
+ return log_oom();
+ }
+ }
+
+ s->state = STDOUT_STREAM_PRIORITY;
+ return 0;
+
+ case STDOUT_STREAM_PRIORITY:
+ r = safe_atoi(p, &s->priority);
+ if (r < 0 || s->priority <= 0 || s->priority >= 999) {
+ log_warning("Failed to parse log priority line.");
+ return -EINVAL;
+ }
+
+ s->state = STDOUT_STREAM_LEVEL_PREFIX;
+ return 0;
+
+ case STDOUT_STREAM_LEVEL_PREFIX:
+ r = parse_boolean(p);
+ if (r < 0) {
+ log_warning("Failed to parse level prefix line.");
+ return -EINVAL;
+ }
+
+ s->level_prefix = !!r;
+ s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG;
+ return 0;
+
+ case STDOUT_STREAM_FORWARD_TO_SYSLOG:
+ r = parse_boolean(p);
+ if (r < 0) {
+ log_warning("Failed to parse forward to syslog line.");
+ return -EINVAL;
+ }
+
+ s->forward_to_syslog = !!r;
+ s->state = STDOUT_STREAM_FORWARD_TO_KMSG;
+ return 0;
+
+ case STDOUT_STREAM_FORWARD_TO_KMSG:
+ r = parse_boolean(p);
+ if (r < 0) {
+ log_warning("Failed to parse copy to kmsg line.");
+ return -EINVAL;
+ }
+
+ s->forward_to_kmsg = !!r;
+ s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE;
+ return 0;
+
+ case STDOUT_STREAM_FORWARD_TO_CONSOLE:
+ r = parse_boolean(p);
+ if (r < 0) {
+ log_warning("Failed to parse copy to console line.");
+ return -EINVAL;
+ }
+
+ s->forward_to_console = !!r;
+ s->state = STDOUT_STREAM_RUNNING;
+ return 0;
+
+ case STDOUT_STREAM_RUNNING:
+ return stdout_stream_log(s, p);
+ }
+
+ assert_not_reached("Unknown stream state");
+}
+
+static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
+ char *p;
+ size_t remaining;
+ int r;
+
+ assert(s);
+
+ p = s->buffer;
+ remaining = s->length;
+ for (;;) {
+ char *end;
+ size_t skip;
+
+ end = memchr(p, '\n', remaining);
+ if (end)
+ skip = end - p + 1;
+ else if (remaining >= sizeof(s->buffer) - 1) {
+ end = p + sizeof(s->buffer) - 1;
+ skip = remaining;
+ } else
+ break;
+
+ *end = 0;
+
+ r = stdout_stream_line(s, p);
+ if (r < 0)
+ return r;
+
+ remaining -= skip;
+ p += skip;
+ }
+
+ if (force_flush && remaining > 0) {
+ p[remaining] = 0;
+ r = stdout_stream_line(s, p);
+ if (r < 0)
+ return r;
+
+ p += remaining;
+ remaining = 0;
+ }
+
+ if (p > s->buffer) {
+ memmove(s->buffer, p, remaining);
+ s->length = remaining;
+ }
+
+ return 0;
+}
+
+int stdout_stream_process(StdoutStream *s) {
+ ssize_t l;
+ int r;
+
+ assert(s);
+
+ l = read(s->fd, s->buffer+s->length, sizeof(s->buffer)-1-s->length);
+ if (l < 0) {
+
+ if (errno == EAGAIN)
+ return 0;
+
+ log_warning("Failed to read from stream: %m");
+ return -errno;
+ }
+
+ if (l == 0) {
+ r = stdout_stream_scan(s, true);
+ if (r < 0)
+ return r;
+
+ return 0;
+ }
+
+ s->length += l;
+ r = stdout_stream_scan(s, false);
+ if (r < 0)
+ return r;
+
+ return 1;
+
+}
+
+void stdout_stream_free(StdoutStream *s) {
+ assert(s);
+
+ if (s->server) {
+ assert(s->server->n_stdout_streams > 0);
+ s->server->n_stdout_streams --;
+ LIST_REMOVE(StdoutStream, stdout_stream, s->server->stdout_streams, s);
+ }
+
+ if (s->fd >= 0) {
+ if (s->server)
+ epoll_ctl(s->server->epoll_fd, EPOLL_CTL_DEL, s->fd, NULL);
+
+ close_nointr_nofail(s->fd);
+ }
+
+#ifdef HAVE_SELINUX
+ if (s->security_context)
+ freecon(s->security_context);
+#endif
+
+ free(s->identifier);
+ free(s);
+}
+
+int stdout_stream_new(Server *s) {
+ StdoutStream *stream;
+ int fd, r;
+ socklen_t len;
+ struct epoll_event ev;
+
+ assert(s);
+
+ fd = accept4(s->stdout_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+ if (fd < 0) {
+ if (errno == EAGAIN)
+ return 0;
+
+ log_error("Failed to accept stdout connection: %m");
+ return -errno;
+ }
+
+ if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) {
+ log_warning("Too many stdout streams, refusing connection.");
+ close_nointr_nofail(fd);
+ return 0;
+ }
+
+ stream = new0(StdoutStream, 1);
+ if (!stream) {
+ close_nointr_nofail(fd);
+ return log_oom();
+ }
+
+ stream->fd = fd;
+
+ len = sizeof(stream->ucred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &stream->ucred, &len) < 0) {
+ log_error("Failed to determine peer credentials: %m");
+ r = -errno;
+ goto fail;
+ }
+
+#ifdef HAVE_SELINUX
+ if (getpeercon(fd, &stream->security_context) < 0 && errno != ENOPROTOOPT)
+ log_error("Failed to determine peer security context: %m");
+#endif
+
+ if (shutdown(fd, SHUT_WR) < 0) {
+ log_error("Failed to shutdown writing side of socket: %m");
+ r = -errno;
+ goto fail;
+ }
+
+ zero(ev);
+ ev.data.ptr = stream;
+ ev.events = EPOLLIN;
+ if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+ log_error("Failed to add stream to event loop: %m");
+ r = -errno;
+ goto fail;
+ }
+
+ stream->server = s;
+ LIST_PREPEND(StdoutStream, stdout_stream, s->stdout_streams, stream);
+ s->n_stdout_streams ++;
+
+ return 0;
+
+fail:
+ stdout_stream_free(stream);
+ return r;
+}
+
+int server_open_stdout_socket(Server *s) {
+ union sockaddr_union sa;
+ int r;
+ struct epoll_event ev;
+
+ assert(s);
+
+ if (s->stdout_fd < 0) {
+
+ s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (s->stdout_fd < 0) {
+ log_error("socket() failed: %m");
+ return -errno;
+ }
+
+ zero(sa);
+ sa.un.sun_family = AF_UNIX;
+ strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path));
+
+ unlink(sa.un.sun_path);
+
+ r = bind(s->stdout_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
+ if (r < 0) {
+ log_error("bind() failed: %m");
+ return -errno;
+ }
+
+ chmod(sa.un.sun_path, 0666);
+
+ if (listen(s->stdout_fd, SOMAXCONN) < 0) {
+ log_error("liste() failed: %m");
+ return -errno;
+ }
+ } else
+ fd_nonblock(s->stdout_fd, 1);
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.fd = s->stdout_fd;
+ if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->stdout_fd, &ev) < 0) {
+ log_error("Failed to add stdout server fd to epoll object: %m");
+ return -errno;
+ }
+
+ return 0;
+}
diff --git a/src/journal/journald-stream.h b/src/journal/journald-stream.h
new file mode 100644
index 0000000000..dfb6267bf6
--- /dev/null
+++ b/src/journal/journald-stream.h
@@ -0,0 +1,30 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journald-server.h"
+
+int server_open_stdout_socket(Server *s);
+
+int stdout_stream_new(Server *s);
+void stdout_stream_free(StdoutStream *s);
+int stdout_stream_process(StdoutStream *s);
diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c
new file mode 100644
index 0000000000..afddca3630
--- /dev/null
+++ b/src/journal/journald-syslog.c
@@ -0,0 +1,492 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <stddef.h>
+#include <sys/epoll.h>
+
+#include "systemd/sd-messages.h"
+#include "socket-util.h"
+#include "journald-server.h"
+#include "journald-syslog.h"
+#include "journald-kmsg.h"
+#include "journald-console.h"
+
+/* Warn once every 30s if we missed syslog message */
+#define WARN_FORWARD_SYSLOG_MISSED_USEC (30 * USEC_PER_SEC)
+
+static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, struct ucred *ucred, struct timeval *tv) {
+ struct msghdr msghdr;
+ struct cmsghdr *cmsg;
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+ } control;
+ union sockaddr_union sa;
+
+ assert(s);
+ assert(iovec);
+ assert(n_iovec > 0);
+
+ zero(msghdr);
+ msghdr.msg_iov = (struct iovec*) iovec;
+ msghdr.msg_iovlen = n_iovec;
+
+ zero(sa);
+ sa.un.sun_family = AF_UNIX;
+ strncpy(sa.un.sun_path, "/run/systemd/journal/syslog", sizeof(sa.un.sun_path));
+ msghdr.msg_name = &sa;
+ msghdr.msg_namelen = offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path);
+
+ if (ucred) {
+ zero(control);
+ msghdr.msg_control = &control;
+ msghdr.msg_controllen = sizeof(control);
+
+ cmsg = CMSG_FIRSTHDR(&msghdr);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_CREDENTIALS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ memcpy(CMSG_DATA(cmsg), ucred, sizeof(struct ucred));
+ msghdr.msg_controllen = cmsg->cmsg_len;
+ }
+
+ /* Forward the syslog message we received via /dev/log to
+ * /run/systemd/syslog. Unfortunately we currently can't set
+ * the SO_TIMESTAMP auxiliary data, and hence we don't. */
+
+ if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0)
+ return;
+
+ /* The socket is full? I guess the syslog implementation is
+ * too slow, and we shouldn't wait for that... */
+ if (errno == EAGAIN) {
+ s->n_forward_syslog_missed++;
+ return;
+ }
+
+ if (ucred && errno == ESRCH) {
+ struct ucred u;
+
+ /* Hmm, presumably the sender process vanished
+ * by now, so let's fix it as good as we
+ * can, and retry */
+
+ u = *ucred;
+ u.pid = getpid();
+ memcpy(CMSG_DATA(cmsg), &u, sizeof(struct ucred));
+
+ if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0)
+ return;
+
+ if (errno == EAGAIN) {
+ s->n_forward_syslog_missed++;
+ return;
+ }
+ }
+
+ if (errno != ENOENT)
+ log_debug("Failed to forward syslog message: %m");
+}
+
+static void forward_syslog_raw(Server *s, int priority, const char *buffer, struct ucred *ucred, struct timeval *tv) {
+ struct iovec iovec;
+
+ assert(s);
+ assert(buffer);
+
+ if (LOG_PRI(priority) > s->max_level_syslog)
+ return;
+
+ IOVEC_SET_STRING(iovec, buffer);
+ forward_syslog_iovec(s, &iovec, 1, ucred, tv);
+}
+
+void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred, struct timeval *tv) {
+ struct iovec iovec[5];
+ char header_priority[6], header_time[64], header_pid[16];
+ int n = 0;
+ time_t t;
+ struct tm *tm;
+ char *ident_buf = NULL;
+
+ assert(s);
+ assert(priority >= 0);
+ assert(priority <= 999);
+ assert(message);
+
+ if (LOG_PRI(priority) > s->max_level_syslog)
+ return;
+
+ /* First: priority field */
+ snprintf(header_priority, sizeof(header_priority), "<%i>", priority);
+ char_array_0(header_priority);
+ IOVEC_SET_STRING(iovec[n++], header_priority);
+
+ /* Second: timestamp */
+ t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
+ tm = localtime(&t);
+ if (!tm)
+ return;
+ if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0)
+ return;
+ IOVEC_SET_STRING(iovec[n++], header_time);
+
+ /* Third: identifier and PID */
+ if (ucred) {
+ if (!identifier) {
+ get_process_comm(ucred->pid, &ident_buf);
+ identifier = ident_buf;
+ }
+
+ snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid);
+ char_array_0(header_pid);
+
+ if (identifier)
+ IOVEC_SET_STRING(iovec[n++], identifier);
+
+ IOVEC_SET_STRING(iovec[n++], header_pid);
+ } else if (identifier) {
+ IOVEC_SET_STRING(iovec[n++], identifier);
+ IOVEC_SET_STRING(iovec[n++], ": ");
+ }
+
+ /* Fourth: message */
+ IOVEC_SET_STRING(iovec[n++], message);
+
+ forward_syslog_iovec(s, iovec, n, ucred, tv);
+
+ free(ident_buf);
+}
+
+int syslog_fixup_facility(int priority) {
+
+ if ((priority & LOG_FACMASK) == 0)
+ return (priority & LOG_PRIMASK) | LOG_USER;
+
+ return priority;
+}
+
+size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid) {
+ const char *p;
+ char *t;
+ size_t l, e;
+
+ assert(buf);
+ assert(identifier);
+ assert(pid);
+
+ p = *buf;
+
+ p += strspn(p, WHITESPACE);
+ l = strcspn(p, WHITESPACE);
+
+ if (l <= 0 ||
+ p[l-1] != ':')
+ return 0;
+
+ e = l;
+ l--;
+
+ if (p[l-1] == ']') {
+ size_t k = l-1;
+
+ for (;;) {
+
+ if (p[k] == '[') {
+ t = strndup(p+k+1, l-k-2);
+ if (t)
+ *pid = t;
+
+ l = k;
+ break;
+ }
+
+ if (k == 0)
+ break;
+
+ k--;
+ }
+ }
+
+ t = strndup(p, l);
+ if (t)
+ *identifier = t;
+
+ e += strspn(p + e, WHITESPACE);
+ *buf = p + e;
+ return e;
+}
+
+void syslog_parse_priority(char **p, int *priority) {
+ int a = 0, b = 0, c = 0;
+ int k;
+
+ assert(p);
+ assert(*p);
+ assert(priority);
+
+ if ((*p)[0] != '<')
+ return;
+
+ if (!strchr(*p, '>'))
+ return;
+
+ if ((*p)[2] == '>') {
+ c = undecchar((*p)[1]);
+ k = 3;
+ } else if ((*p)[3] == '>') {
+ b = undecchar((*p)[1]);
+ c = undecchar((*p)[2]);
+ k = 4;
+ } else if ((*p)[4] == '>') {
+ a = undecchar((*p)[1]);
+ b = undecchar((*p)[2]);
+ c = undecchar((*p)[3]);
+ k = 5;
+ } else
+ return;
+
+ if (a < 0 || b < 0 || c < 0)
+ return;
+
+ *priority = a*100+b*10+c;
+ *p += k;
+}
+
+static void syslog_skip_date(char **buf) {
+ enum {
+ LETTER,
+ SPACE,
+ NUMBER,
+ SPACE_OR_NUMBER,
+ COLON
+ } sequence[] = {
+ LETTER, LETTER, LETTER,
+ SPACE,
+ SPACE_OR_NUMBER, NUMBER,
+ SPACE,
+ SPACE_OR_NUMBER, NUMBER,
+ COLON,
+ SPACE_OR_NUMBER, NUMBER,
+ COLON,
+ SPACE_OR_NUMBER, NUMBER,
+ SPACE
+ };
+
+ char *p;
+ unsigned i;
+
+ assert(buf);
+ assert(*buf);
+
+ p = *buf;
+
+ for (i = 0; i < ELEMENTSOF(sequence); i++, p++) {
+
+ if (!*p)
+ return;
+
+ switch (sequence[i]) {
+
+ case SPACE:
+ if (*p != ' ')
+ return;
+ break;
+
+ case SPACE_OR_NUMBER:
+ if (*p == ' ')
+ break;
+
+ /* fall through */
+
+ case NUMBER:
+ if (*p < '0' || *p > '9')
+ return;
+
+ break;
+
+ case LETTER:
+ if (!(*p >= 'A' && *p <= 'Z') &&
+ !(*p >= 'a' && *p <= 'z'))
+ return;
+
+ break;
+
+ case COLON:
+ if (*p != ':')
+ return;
+ break;
+
+ }
+ }
+
+ *buf = p;
+}
+
+void server_process_syslog_message(
+ Server *s,
+ const char *buf,
+ struct ucred *ucred,
+ struct timeval *tv,
+ const char *label,
+ size_t label_len) {
+
+ char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *syslog_pid = NULL;
+ struct iovec iovec[N_IOVEC_META_FIELDS + 6];
+ unsigned n = 0;
+ int priority = LOG_USER | LOG_INFO;
+ char *identifier = NULL, *pid = NULL;
+ const char *orig;
+
+ assert(s);
+ assert(buf);
+
+ orig = buf;
+ syslog_parse_priority((char**) &buf, &priority);
+
+ if (s->forward_to_syslog)
+ forward_syslog_raw(s, priority, orig, ucred, tv);
+
+ syslog_skip_date((char**) &buf);
+ syslog_parse_identifier(&buf, &identifier, &pid);
+
+ if (s->forward_to_kmsg)
+ server_forward_kmsg(s, priority, identifier, buf, ucred);
+
+ if (s->forward_to_console)
+ server_forward_console(s, priority, identifier, buf, ucred);
+
+ IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog");
+
+ if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0)
+ IOVEC_SET_STRING(iovec[n++], syslog_priority);
+
+ if (priority & LOG_FACMASK)
+ if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0)
+ IOVEC_SET_STRING(iovec[n++], syslog_facility);
+
+ if (identifier) {
+ syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier);
+ if (syslog_identifier)
+ IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+ }
+
+ if (pid) {
+ syslog_pid = strappend("SYSLOG_PID=", pid);
+ if (syslog_pid)
+ IOVEC_SET_STRING(iovec[n++], syslog_pid);
+ }
+
+ message = strappend("MESSAGE=", buf);
+ if (message)
+ IOVEC_SET_STRING(iovec[n++], message);
+
+ server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority);
+
+ free(message);
+ free(identifier);
+ free(pid);
+ free(syslog_priority);
+ free(syslog_facility);
+ free(syslog_identifier);
+ free(syslog_pid);
+}
+
+int server_open_syslog_socket(Server *s) {
+ union sockaddr_union sa;
+ int one, r;
+ struct epoll_event ev;
+
+ assert(s);
+
+ if (s->syslog_fd < 0) {
+
+ s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (s->syslog_fd < 0) {
+ log_error("socket() failed: %m");
+ return -errno;
+ }
+
+ zero(sa);
+ sa.un.sun_family = AF_UNIX;
+ strncpy(sa.un.sun_path, "/dev/log", sizeof(sa.un.sun_path));
+
+ unlink(sa.un.sun_path);
+
+ r = bind(s->syslog_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
+ if (r < 0) {
+ log_error("bind() failed: %m");
+ return -errno;
+ }
+
+ chmod(sa.un.sun_path, 0666);
+ } else
+ fd_nonblock(s->syslog_fd, 1);
+
+ one = 1;
+ r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ if (r < 0) {
+ log_error("SO_PASSCRED failed: %m");
+ return -errno;
+ }
+
+#ifdef HAVE_SELINUX
+ one = 1;
+ r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one));
+ if (r < 0)
+ log_warning("SO_PASSSEC failed: %m");
+#endif
+
+ one = 1;
+ r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
+ if (r < 0) {
+ log_error("SO_TIMESTAMP failed: %m");
+ return -errno;
+ }
+
+ zero(ev);
+ ev.events = EPOLLIN;
+ ev.data.fd = s->syslog_fd;
+ if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->syslog_fd, &ev) < 0) {
+ log_error("Failed to add syslog server fd to epoll object: %m");
+ return -errno;
+ }
+
+ return 0;
+}
+
+void server_maybe_warn_forward_syslog_missed(Server *s) {
+ usec_t n;
+ assert(s);
+
+ if (s->n_forward_syslog_missed <= 0)
+ return;
+
+ n = now(CLOCK_MONOTONIC);
+ if (s->last_warn_forward_syslog_missed + WARN_FORWARD_SYSLOG_MISSED_USEC > n)
+ return;
+
+ server_driver_message(s, SD_MESSAGE_FORWARD_SYSLOG_MISSED, "Forwarding to syslog missed %u messages.", s->n_forward_syslog_missed);
+
+ s->n_forward_syslog_missed = 0;
+ s->last_warn_forward_syslog_missed = n;
+}
diff --git a/src/journal/journald-syslog.h b/src/journal/journald-syslog.h
new file mode 100644
index 0000000000..7ff215b524
--- /dev/null
+++ b/src/journal/journald-syslog.h
@@ -0,0 +1,36 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journald-server.h"
+
+int syslog_fixup_facility(int priority);
+
+void syslog_parse_priority(char **p, int *priority);
+size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid);
+
+void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred, struct timeval *tv);
+
+void server_process_syslog_message(Server *s, const char *buf, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len);
+int server_open_syslog_socket(Server *s);
+
+void server_maybe_warn_forward_syslog_missed(Server *s);
diff --git a/src/journal/journald.c b/src/journal/journald.c
new file mode 100644
index 0000000000..d6b9be5974
--- /dev/null
+++ b/src/journal/journald.c
@@ -0,0 +1,140 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <systemd/sd-journal.h>
+#include <systemd/sd-messages.h>
+#include <systemd/sd-daemon.h>
+
+#include "journal-authenticate.h"
+#include "journald-server.h"
+#include "journald-kmsg.h"
+#include "journald-syslog.h"
+
+int main(int argc, char *argv[]) {
+ Server server;
+ int r;
+
+ /* if (getppid() != 1) { */
+ /* log_error("This program should be invoked by init only."); */
+ /* return EXIT_FAILURE; */
+ /* } */
+
+ if (argc > 1) {
+ log_error("This program does not take arguments.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_SAFE);
+ log_set_facility(LOG_SYSLOG);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = server_init(&server);
+ if (r < 0)
+ goto finish;
+
+ server_vacuum(&server);
+ server_flush_to_var(&server);
+ server_flush_dev_kmsg(&server);
+
+ log_debug("systemd-journald running as pid %lu", (unsigned long) getpid());
+ server_driver_message(&server, SD_MESSAGE_JOURNAL_START, "Journal started");
+
+ sd_notify(false,
+ "READY=1\n"
+ "STATUS=Processing requests...");
+
+ for (;;) {
+ struct epoll_event event;
+ int t = -1;
+ usec_t n;
+
+ n = now(CLOCK_REALTIME);
+
+ if (server.max_retention_usec > 0 && server.oldest_file_usec > 0) {
+
+ /* The retention time is reached, so let's vacuum! */
+ if (server.oldest_file_usec + server.max_retention_usec < n) {
+ log_info("Retention time reached.");
+ server_rotate(&server);
+ server_vacuum(&server);
+ continue;
+ }
+
+ /* Calculate when to rotate the next time */
+ t = (int) ((server.oldest_file_usec + server.max_retention_usec - n + USEC_PER_MSEC - 1) / USEC_PER_MSEC);
+ log_info("Sleeping for %i ms", t);
+ }
+
+#ifdef HAVE_GCRYPT
+ if (server.system_journal) {
+ usec_t u;
+
+ if (journal_file_next_evolve_usec(server.system_journal, &u)) {
+ if (n >= u)
+ t = 0;
+ else
+ t = MIN(t, (int) ((u - n + USEC_PER_MSEC - 1) / USEC_PER_MSEC));
+ }
+ }
+#endif
+
+ r = epoll_wait(server.epoll_fd, &event, 1, t);
+ if (r < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ log_error("epoll_wait() failed: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ if (r > 0) {
+ r = process_event(&server, &event);
+ if (r < 0)
+ goto finish;
+ else if (r == 0)
+ break;
+ }
+
+ server_maybe_append_tags(&server);
+ server_maybe_warn_forward_syslog_missed(&server);
+ }
+
+ log_debug("systemd-journald stopped as pid %lu", (unsigned long) getpid());
+ server_driver_message(&server, SD_MESSAGE_JOURNAL_STOP, "Journal stopped");
+
+finish:
+ sd_notify(false,
+ "STATUS=Shutting down...");
+
+ server_done(&server);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/journal/journald.conf b/src/journal/journald.conf
new file mode 100644
index 0000000000..948318bc62
--- /dev/null
+++ b/src/journal/journald.conf
@@ -0,0 +1,32 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# See journald.conf(5) for details
+
+[Journal]
+#Storage=auto
+#Compress=yes
+#Seal=yes
+#SplitMode=login
+#RateLimitInterval=10s
+#RateLimitBurst=200
+#SystemMaxUse=
+#SystemKeepFree=
+#SystemMaxFileSize=
+#RuntimeMaxUse=
+#RuntimeKeepFree=
+#RuntimeMaxFileSize=
+#MaxRetentionSec=
+#MaxFileSec=1month
+#ForwardToSyslog=yes
+#ForwardToKMsg=no
+#ForwardToConsole=no
+#TTYPath=/dev/console
+#MaxLevelStore=debug
+#MaxLevelSyslog=debug
+#MaxLevelKMsg=notice
+#MaxLevelConsole=info
diff --git a/src/journal/libsystemd-journal.pc.in b/src/journal/libsystemd-journal.pc.in
new file mode 100644
index 0000000000..9883595644
--- /dev/null
+++ b/src/journal/libsystemd-journal.pc.in
@@ -0,0 +1,19 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: systemd
+Description: systemd Journal Utility Library
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Requires: libsystemd-id128 = @PACKAGE_VERSION@
+Libs: -L${libdir} -lsystemd-journal
+Cflags: -I${includedir}
diff --git a/src/journal/libsystemd-journal.sym b/src/journal/libsystemd-journal.sym
new file mode 100644
index 0000000000..ad78fcc74d
--- /dev/null
+++ b/src/journal/libsystemd-journal.sym
@@ -0,0 +1,87 @@
+/***
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+***/
+
+/* Original symbols from systemd v38 */
+
+LIBSYSTEMD_JOURNAL_38 {
+global:
+ sd_journal_print;
+ sd_journal_printv;
+ sd_journal_send;
+ sd_journal_sendv;
+ sd_journal_stream_fd;
+ sd_journal_open;
+ sd_journal_close;
+ sd_journal_previous;
+ sd_journal_next;
+ sd_journal_previous_skip;
+ sd_journal_next_skip;
+ sd_journal_get_realtime_usec;
+ sd_journal_get_monotonic_usec;
+ sd_journal_get_data;
+ sd_journal_enumerate_data;
+ sd_journal_restart_data;
+ sd_journal_add_match;
+ sd_journal_flush_matches;
+ sd_journal_seek_head;
+ sd_journal_seek_tail;
+ sd_journal_seek_monotonic_usec;
+ sd_journal_seek_realtime_usec;
+ sd_journal_seek_cursor;
+ sd_journal_get_cursor;
+ sd_journal_get_fd;
+ sd_journal_process;
+local:
+ *;
+};
+
+LIBSYSTEMD_JOURNAL_183 {
+global:
+ sd_journal_print_with_location;
+ sd_journal_printv_with_location;
+ sd_journal_send_with_location;
+ sd_journal_sendv_with_location;
+} LIBSYSTEMD_JOURNAL_38;
+
+LIBSYSTEMD_JOURNAL_184 {
+global:
+ sd_journal_get_cutoff_realtime_usec;
+ sd_journal_get_cutoff_monotonic_usec;
+} LIBSYSTEMD_JOURNAL_183;
+
+LIBSYSTEMD_JOURNAL_187 {
+global:
+ sd_journal_wait;
+ sd_journal_open_directory;
+ sd_journal_add_disjunction;
+} LIBSYSTEMD_JOURNAL_184;
+
+LIBSYSTEMD_JOURNAL_188 {
+global:
+ sd_journal_perror;
+ sd_journal_perror_with_location;
+} LIBSYSTEMD_JOURNAL_187;
+
+LIBSYSTEMD_JOURNAL_190 {
+global:
+ sd_journal_get_usage;
+} LIBSYSTEMD_JOURNAL_188;
+
+LIBSYSTEMD_JOURNAL_195 {
+global:
+ sd_journal_test_cursor;
+ sd_journal_query_unique;
+ sd_journal_enumerate_unique;
+ sd_journal_restart_unique;
+} LIBSYSTEMD_JOURNAL_190;
+
+LIBSYSTEMD_JOURNAL_196 {
+global:
+ sd_journal_fd_reliable;
+} LIBSYSTEMD_JOURNAL_195;
diff --git a/src/journal/lookup3.c b/src/journal/lookup3.c
new file mode 100644
index 0000000000..52ffdf7b1d
--- /dev/null
+++ b/src/journal/lookup3.c
@@ -0,0 +1,1009 @@
+/* Slightly modified by Lennart Poettering, to avoid name clashes, and
+ * unexport a few functions. */
+
+#include "lookup3.h"
+
+/*
+-------------------------------------------------------------------------------
+lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+
+These are functions for producing 32-bit hashes for hash table lookup.
+hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+are externally useful functions. Routines to test the hash are included
+if SELF_TEST is defined. You can use this free for any purpose. It's in
+the public domain. It has no warranty.
+
+You probably want to use hashlittle(). hashlittle() and hashbig()
+hash byte arrays. hashlittle() is faster than hashbig() on
+little-endian machines. Intel and AMD are little-endian machines.
+On second thought, you probably want hashlittle2(), which is identical to
+hashlittle() except it returns two 32-bit hashes for the price of one.
+You could implement hashbig2() if you wanted but I haven't bothered here.
+
+If you want to find a hash of, say, exactly 7 integers, do
+ a = i1; b = i2; c = i3;
+ mix(a,b,c);
+ a += i4; b += i5; c += i6;
+ mix(a,b,c);
+ a += i7;
+ final(a,b,c);
+then use c as the hash value. If you have a variable length array of
+4-byte integers to hash, use hashword(). If you have a byte array (like
+a character string), use hashlittle(). If you have several byte arrays, or
+a mix of things, see the comments above hashlittle().
+
+Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
+then mix those integers. This is fast (you can do a lot more thorough
+mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+-------------------------------------------------------------------------------
+*/
+/* #define SELF_TEST 1 */
+
+#include <stdio.h> /* defines printf for tests */
+#include <time.h> /* defines time_t for timings in the test */
+#include <stdint.h> /* defines uint32_t etc */
+#include <sys/param.h> /* attempt to define endianness */
+#ifdef linux
+# include <endian.h> /* attempt to define endianness */
+#endif
+
+/*
+ * My best guess at if you are big-endian or little-endian. This may
+ * need adjustment.
+ */
+#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \
+ __BYTE_ORDER == __LITTLE_ENDIAN) || \
+ (defined(i386) || defined(__i386__) || defined(__i486__) || \
+ defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL))
+# define HASH_LITTLE_ENDIAN 1
+# define HASH_BIG_ENDIAN 0
+#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \
+ __BYTE_ORDER == __BIG_ENDIAN) || \
+ (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel))
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 1
+#else
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 0
+#endif
+
+#define hashsize(n) ((uint32_t)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+/*
+-------------------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+
+This is reversible, so any information in (a,b,c) before mix() is
+still in (a,b,c) after mix().
+
+If four pairs of (a,b,c) inputs are run through mix(), or through
+mix() in reverse, there are at least 32 bits of the output that
+are sometimes the same for one pair and different for another pair.
+This was tested for:
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
+satisfy this are
+ 4 6 8 16 19 4
+ 9 15 3 18 27 15
+ 14 9 3 7 17 3
+Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+for "differ" defined as + with a one-bit base and a two-bit delta. I
+used http://burtleburtle.net/bob/hash/avalanche.html to choose
+the operations, constants, and arrangements of the variables.
+
+This does not achieve avalanche. There are input bits of (a,b,c)
+that fail to affect some output bits of (a,b,c), especially of a. The
+most thoroughly mixed value is c, but it doesn't really even achieve
+avalanche in c.
+
+This allows some parallelism. Read-after-writes are good at doubling
+the number of bits affected, so the goal of mixing pulls in the opposite
+direction as the goal of parallelism. I did what I could. Rotates
+seem to cost as much as shifts on every machine I could lay my hands
+on, and rotates are much kinder to the top and bottom bits, so I used
+rotates.
+-------------------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+ a -= c; a ^= rot(c, 4); c += b; \
+ b -= a; b ^= rot(a, 6); a += c; \
+ c -= b; c ^= rot(b, 8); b += a; \
+ a -= c; a ^= rot(c,16); c += b; \
+ b -= a; b ^= rot(a,19); a += c; \
+ c -= b; c ^= rot(b, 4); b += a; \
+}
+
+/*
+-------------------------------------------------------------------------------
+final -- final mixing of 3 32-bit values (a,b,c) into c
+
+Pairs of (a,b,c) values differing in only a few bits will usually
+produce values of c that look totally different. This was tested for
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+These constants passed:
+ 14 11 25 16 4 14 24
+ 12 14 25 16 4 14 24
+and these came close:
+ 4 8 15 26 3 22 24
+ 10 8 15 26 3 22 24
+ 11 8 15 26 3 22 24
+-------------------------------------------------------------------------------
+*/
+#define final(a,b,c) \
+{ \
+ c ^= b; c -= rot(b,14); \
+ a ^= c; a -= rot(c,11); \
+ b ^= a; b -= rot(a,25); \
+ c ^= b; c -= rot(b,16); \
+ a ^= c; a -= rot(c,4); \
+ b ^= a; b -= rot(a,14); \
+ c ^= b; c -= rot(b,24); \
+}
+
+/*
+--------------------------------------------------------------------
+ This works on all machines. To be useful, it requires
+ -- that the key be an array of uint32_t's, and
+ -- that the length be the number of uint32_t's in the key
+
+ The function hashword() is identical to hashlittle() on little-endian
+ machines, and identical to hashbig() on big-endian machines,
+ except that the length has to be measured in uint32_ts rather than in
+ bytes. hashlittle() is more complicated than hashword() only because
+ hashlittle() has to dance around fitting the key bytes into registers.
+--------------------------------------------------------------------
+*/
+uint32_t jenkins_hashword(
+const uint32_t *k, /* the key, an array of uint32_t values */
+size_t length, /* the length of the key, in uint32_ts */
+uint32_t initval) /* the previous hash, or an arbitrary value */
+{
+ uint32_t a,b,c;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval;
+
+ /*------------------------------------------------- handle most of the key */
+ while (length > 3)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 3;
+ k += 3;
+ }
+
+ /*------------------------------------------- handle the last 3 uint32_t's */
+ switch(length) /* all the case statements fall through */
+ {
+ case 3 : c+=k[2];
+ case 2 : b+=k[1];
+ case 1 : a+=k[0];
+ final(a,b,c);
+ case 0: /* case 0: nothing left to add */
+ break;
+ }
+ /*------------------------------------------------------ report the result */
+ return c;
+}
+
+
+/*
+--------------------------------------------------------------------
+hashword2() -- same as hashword(), but take two seeds and return two
+32-bit values. pc and pb must both be nonnull, and *pc and *pb must
+both be initialized with seeds. If you pass in (*pb)==0, the output
+(*pc) will be the same as the return value from hashword().
+--------------------------------------------------------------------
+*/
+void jenkins_hashword2 (
+const uint32_t *k, /* the key, an array of uint32_t values */
+size_t length, /* the length of the key, in uint32_ts */
+uint32_t *pc, /* IN: seed OUT: primary hash value */
+uint32_t *pb) /* IN: more seed OUT: secondary hash value */
+{
+ uint32_t a,b,c;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)(length<<2)) + *pc;
+ c += *pb;
+
+ /*------------------------------------------------- handle most of the key */
+ while (length > 3)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 3;
+ k += 3;
+ }
+
+ /*------------------------------------------- handle the last 3 uint32_t's */
+ switch(length) /* all the case statements fall through */
+ {
+ case 3 : c+=k[2];
+ case 2 : b+=k[1];
+ case 1 : a+=k[0];
+ final(a,b,c);
+ case 0: /* case 0: nothing left to add */
+ break;
+ }
+ /*------------------------------------------------------ report the result */
+ *pc=c; *pb=b;
+}
+
+
+/*
+-------------------------------------------------------------------------------
+hashlittle() -- hash a variable-length key into a 32-bit value
+ k : the key (the unaligned variable-length array of bytes)
+ length : the length of the key, counting by bytes
+ initval : can be any 4-byte value
+Returns a 32-bit value. Every bit of the key affects every bit of
+the return value. Two keys differing by one or two bits will have
+totally different hash values.
+
+The best hash table sizes are powers of 2. There is no need to do
+mod a prime (mod is sooo slow!). If you need less than 32 bits,
+use a bitmask. For example, if you need only 10 bits, do
+ h = (h & hashmask(10));
+In which case, the hash table should have hashsize(10) elements.
+
+If you are hashing n strings (uint8_t **)k, do it like this:
+ for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h);
+
+By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this
+code any way you wish, private, educational, or commercial. It's free.
+
+Use for hash table lookup, or anything where one collision in 2^^32 is
+acceptable. Do NOT use for cryptographic purposes.
+-------------------------------------------------------------------------------
+*/
+
+uint32_t jenkins_hashlittle( const void *key, size_t length, uint32_t initval)
+{
+ uint32_t a,b,c; /* internal state */
+ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]&0xffffff" actually reads beyond the end of the string, but
+ * then masks off the part it's not allowed to read. Because the
+ * string is aligned, the masked-off tail is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticeably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff; break;
+ case 2 : a+=k[0]&0xffff; break;
+ case 1 : a+=k[0]&0xff; break;
+ case 0 : return c; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+ {
+ const uint8_t *k8 = (const uint8_t *) k;
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
+ case 1 : a+=k8[0]; break;
+ case 0 : return c;
+ }
+ }
+
+#endif /* !valgrind */
+
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
+ const uint8_t *k8;
+
+ /*--------------- all but last block: aligned reads and different mixing */
+ while (length > 12)
+ {
+ a += k[0] + (((uint32_t)k[1])<<16);
+ b += k[2] + (((uint32_t)k[3])<<16);
+ c += k[4] + (((uint32_t)k[5])<<16);
+ mix(a,b,c);
+ length -= 12;
+ k += 6;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=k[4];
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=k[2];
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=k[0];
+ break;
+ case 1 : a+=k8[0];
+ break;
+ case 0 : return c; /* zero length requires no mixing */
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ a += ((uint32_t)k[1])<<8;
+ a += ((uint32_t)k[2])<<16;
+ a += ((uint32_t)k[3])<<24;
+ b += k[4];
+ b += ((uint32_t)k[5])<<8;
+ b += ((uint32_t)k[6])<<16;
+ b += ((uint32_t)k[7])<<24;
+ c += k[8];
+ c += ((uint32_t)k[9])<<8;
+ c += ((uint32_t)k[10])<<16;
+ c += ((uint32_t)k[11])<<24;
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=((uint32_t)k[11])<<24;
+ case 11: c+=((uint32_t)k[10])<<16;
+ case 10: c+=((uint32_t)k[9])<<8;
+ case 9 : c+=k[8];
+ case 8 : b+=((uint32_t)k[7])<<24;
+ case 7 : b+=((uint32_t)k[6])<<16;
+ case 6 : b+=((uint32_t)k[5])<<8;
+ case 5 : b+=k[4];
+ case 4 : a+=((uint32_t)k[3])<<24;
+ case 3 : a+=((uint32_t)k[2])<<16;
+ case 2 : a+=((uint32_t)k[1])<<8;
+ case 1 : a+=k[0];
+ break;
+ case 0 : return c;
+ }
+ }
+
+ final(a,b,c);
+ return c;
+}
+
+
+/*
+ * hashlittle2: return 2 32-bit hash values
+ *
+ * This is identical to hashlittle(), except it returns two 32-bit hash
+ * values instead of just one. This is good enough for hash table
+ * lookup with 2^^64 buckets, or if you want a second hash if you're not
+ * happy with the first, or if you want a probably-unique 64-bit ID for
+ * the key. *pc is better mixed than *pb, so use *pc first. If you want
+ * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
+ */
+void jenkins_hashlittle2(
+ const void *key, /* the key to hash */
+ size_t length, /* length of the key */
+ uint32_t *pc, /* IN: primary initval, OUT: primary hash */
+ uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */
+{
+ uint32_t a,b,c; /* internal state */
+ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc;
+ c += *pb;
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]&0xffffff" actually reads beyond the end of the string, but
+ * then masks off the part it's not allowed to read. Because the
+ * string is aligned, the masked-off tail is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticeably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff; break;
+ case 2 : a+=k[0]&0xffff; break;
+ case 1 : a+=k[0]&0xff; break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+
+ {
+ const uint8_t *k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
+ case 1 : a+=k8[0]; break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+ }
+
+#endif /* !valgrind */
+
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
+ const uint8_t *k8;
+
+ /*--------------- all but last block: aligned reads and different mixing */
+ while (length > 12)
+ {
+ a += k[0] + (((uint32_t)k[1])<<16);
+ b += k[2] + (((uint32_t)k[3])<<16);
+ c += k[4] + (((uint32_t)k[5])<<16);
+ mix(a,b,c);
+ length -= 12;
+ k += 6;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=k[4];
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=k[2];
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=k[0];
+ break;
+ case 1 : a+=k8[0];
+ break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ a += ((uint32_t)k[1])<<8;
+ a += ((uint32_t)k[2])<<16;
+ a += ((uint32_t)k[3])<<24;
+ b += k[4];
+ b += ((uint32_t)k[5])<<8;
+ b += ((uint32_t)k[6])<<16;
+ b += ((uint32_t)k[7])<<24;
+ c += k[8];
+ c += ((uint32_t)k[9])<<8;
+ c += ((uint32_t)k[10])<<16;
+ c += ((uint32_t)k[11])<<24;
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=((uint32_t)k[11])<<24;
+ case 11: c+=((uint32_t)k[10])<<16;
+ case 10: c+=((uint32_t)k[9])<<8;
+ case 9 : c+=k[8];
+ case 8 : b+=((uint32_t)k[7])<<24;
+ case 7 : b+=((uint32_t)k[6])<<16;
+ case 6 : b+=((uint32_t)k[5])<<8;
+ case 5 : b+=k[4];
+ case 4 : a+=((uint32_t)k[3])<<24;
+ case 3 : a+=((uint32_t)k[2])<<16;
+ case 2 : a+=((uint32_t)k[1])<<8;
+ case 1 : a+=k[0];
+ break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+ }
+
+ final(a,b,c);
+ *pc=c; *pb=b;
+}
+
+
+
+/*
+ * hashbig():
+ * This is the same as hashword() on big-endian machines. It is different
+ * from hashlittle() on all machines. hashbig() takes advantage of
+ * big-endian byte ordering.
+ */
+uint32_t jenkins_hashbig( const void *key, size_t length, uint32_t initval)
+{
+ uint32_t a,b,c;
+ union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
+
+ u.ptr = key;
+ if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]<<8" actually reads beyond the end of the string, but
+ * then shifts out the part it's not allowed to read. Because the
+ * string is aligned, the illegal read is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticeably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff00; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff0000; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff000000; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff00; break;
+ case 2 : a+=k[0]&0xffff0000; break;
+ case 1 : a+=k[0]&0xff000000; break;
+ case 0 : return c; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+
+ {
+ const uint8_t *k8 = (const uint8_t *)k;
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((uint32_t)k8[10])<<8; /* fall through */
+ case 10: c+=((uint32_t)k8[9])<<16; /* fall through */
+ case 9 : c+=((uint32_t)k8[8])<<24; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((uint32_t)k8[6])<<8; /* fall through */
+ case 6 : b+=((uint32_t)k8[5])<<16; /* fall through */
+ case 5 : b+=((uint32_t)k8[4])<<24; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((uint32_t)k8[2])<<8; /* fall through */
+ case 2 : a+=((uint32_t)k8[1])<<16; /* fall through */
+ case 1 : a+=((uint32_t)k8[0])<<24; break;
+ case 0 : return c;
+ }
+ }
+
+#endif /* !VALGRIND */
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += ((uint32_t)k[0])<<24;
+ a += ((uint32_t)k[1])<<16;
+ a += ((uint32_t)k[2])<<8;
+ a += ((uint32_t)k[3]);
+ b += ((uint32_t)k[4])<<24;
+ b += ((uint32_t)k[5])<<16;
+ b += ((uint32_t)k[6])<<8;
+ b += ((uint32_t)k[7]);
+ c += ((uint32_t)k[8])<<24;
+ c += ((uint32_t)k[9])<<16;
+ c += ((uint32_t)k[10])<<8;
+ c += ((uint32_t)k[11]);
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=k[11];
+ case 11: c+=((uint32_t)k[10])<<8;
+ case 10: c+=((uint32_t)k[9])<<16;
+ case 9 : c+=((uint32_t)k[8])<<24;
+ case 8 : b+=k[7];
+ case 7 : b+=((uint32_t)k[6])<<8;
+ case 6 : b+=((uint32_t)k[5])<<16;
+ case 5 : b+=((uint32_t)k[4])<<24;
+ case 4 : a+=k[3];
+ case 3 : a+=((uint32_t)k[2])<<8;
+ case 2 : a+=((uint32_t)k[1])<<16;
+ case 1 : a+=((uint32_t)k[0])<<24;
+ break;
+ case 0 : return c;
+ }
+ }
+
+ final(a,b,c);
+ return c;
+}
+
+
+#ifdef SELF_TEST
+
+/* used for timings */
+void driver1()
+{
+ uint8_t buf[256];
+ uint32_t i;
+ uint32_t h=0;
+ time_t a,z;
+
+ time(&a);
+ for (i=0; i<256; ++i) buf[i] = 'x';
+ for (i=0; i<1; ++i)
+ {
+ h = hashlittle(&buf[0],1,h);
+ }
+ time(&z);
+ if (z-a > 0) printf("time %d %.8x\n", z-a, h);
+}
+
+/* check that every input bit changes every output bit half the time */
+#define HASHSTATE 1
+#define HASHLEN 1
+#define MAXPAIR 60
+#define MAXLEN 70
+void driver2()
+{
+ uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1];
+ uint32_t c[HASHSTATE], d[HASHSTATE], i=0, j=0, k, l, m=0, z;
+ uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE];
+ uint32_t x[HASHSTATE],y[HASHSTATE];
+ uint32_t hlen;
+
+ printf("No more than %d trials should ever be needed \n",MAXPAIR/2);
+ for (hlen=0; hlen < MAXLEN; ++hlen)
+ {
+ z=0;
+ for (i=0; i<hlen; ++i) /*----------------------- for each input byte, */
+ {
+ for (j=0; j<8; ++j) /*------------------------ for each input bit, */
+ {
+ for (m=1; m<8; ++m) /*------------ for serveral possible initvals, */
+ {
+ for (l=0; l<HASHSTATE; ++l)
+ e[l]=f[l]=g[l]=h[l]=x[l]=y[l]=~((uint32_t)0);
+
+ /*---- check that every output bit is affected by that input bit */
+ for (k=0; k<MAXPAIR; k+=2)
+ {
+ uint32_t finished=1;
+ /* keys have one bit different */
+ for (l=0; l<hlen+1; ++l) {a[l] = b[l] = (uint8_t)0;}
+ /* have a and b be two keys differing in only one bit */
+ a[i] ^= (k<<j);
+ a[i] ^= (k>>(8-j));
+ c[0] = hashlittle(a, hlen, m);
+ b[i] ^= ((k+1)<<j);
+ b[i] ^= ((k+1)>>(8-j));
+ d[0] = hashlittle(b, hlen, m);
+ /* check every bit is 1, 0, set, and not set at least once */
+ for (l=0; l<HASHSTATE; ++l)
+ {
+ e[l] &= (c[l]^d[l]);
+ f[l] &= ~(c[l]^d[l]);
+ g[l] &= c[l];
+ h[l] &= ~c[l];
+ x[l] &= d[l];
+ y[l] &= ~d[l];
+ if (e[l]|f[l]|g[l]|h[l]|x[l]|y[l]) finished=0;
+ }
+ if (finished) break;
+ }
+ if (k>z) z=k;
+ if (k==MAXPAIR)
+ {
+ printf("Some bit didn't change: ");
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x ",
+ e[0],f[0],g[0],h[0],x[0],y[0]);
+ printf("i %d j %d m %d len %d\n", i, j, m, hlen);
+ }
+ if (z==MAXPAIR) goto done;
+ }
+ }
+ }
+ done:
+ if (z < MAXPAIR)
+ {
+ printf("Mix success %2d bytes %2d initvals ",i,m);
+ printf("required %d trials\n", z/2);
+ }
+ }
+ printf("\n");
+}
+
+/* Check for reading beyond the end of the buffer and alignment problems */
+void driver3()
+{
+ uint8_t buf[MAXLEN+20], *b;
+ uint32_t len;
+ uint8_t q[] = "This is the time for all good men to come to the aid of their country...";
+ uint32_t h;
+ uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country...";
+ uint32_t i;
+ uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country...";
+ uint32_t j;
+ uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country...";
+ uint32_t ref,x,y;
+ uint8_t *p;
+
+ printf("Endianness. These lines should all be the same (for values filled in):\n");
+ printf("%.8x %.8x %.8x\n",
+ hashword((const uint32_t *)q, (sizeof(q)-1)/4, 13),
+ hashword((const uint32_t *)q, (sizeof(q)-5)/4, 13),
+ hashword((const uint32_t *)q, (sizeof(q)-9)/4, 13));
+ p = q;
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+ hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+ hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+ hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+ hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+ hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+ p = &qq[1];
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+ hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+ hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+ hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+ hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+ hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+ p = &qqq[2];
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+ hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+ hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+ hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+ hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+ hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+ p = &qqqq[3];
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+ hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+ hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+ hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+ hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+ hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+ printf("\n");
+
+ /* check that hashlittle2 and hashlittle produce the same results */
+ i=47; j=0;
+ hashlittle2(q, sizeof(q), &i, &j);
+ if (hashlittle(q, sizeof(q), 47) != i)
+ printf("hashlittle2 and hashlittle mismatch\n");
+
+ /* check that hashword2 and hashword produce the same results */
+ len = 0xdeadbeef;
+ i=47, j=0;
+ hashword2(&len, 1, &i, &j);
+ if (hashword(&len, 1, 47) != i)
+ printf("hashword2 and hashword mismatch %x %x\n",
+ i, hashword(&len, 1, 47));
+
+ /* check hashlittle doesn't read before or after the ends of the string */
+ for (h=0, b=buf+1; h<8; ++h, ++b)
+ {
+ for (i=0; i<MAXLEN; ++i)
+ {
+ len = i;
+ for (j=0; j<i; ++j) *(b+j)=0;
+
+ /* these should all be equal */
+ ref = hashlittle(b, len, (uint32_t)1);
+ *(b+i)=(uint8_t)~0;
+ *(b-1)=(uint8_t)~0;
+ x = hashlittle(b, len, (uint32_t)1);
+ y = hashlittle(b, len, (uint32_t)1);
+ if ((ref != x) || (ref != y))
+ {
+ printf("alignment error: %.8x %.8x %.8x %d %d\n",ref,x,y,
+ h, i);
+ }
+ }
+ }
+}
+
+/* check for problems with nulls */
+ void driver4()
+{
+ uint8_t buf[1];
+ uint32_t h,i,state[HASHSTATE];
+
+
+ buf[0] = ~0;
+ for (i=0; i<HASHSTATE; ++i) state[i] = 1;
+ printf("These should all be different\n");
+ for (i=0, h=0; i<8; ++i)
+ {
+ h = hashlittle(buf, 0, h);
+ printf("%2ld 0-byte strings, hash is %.8x\n", i, h);
+ }
+}
+
+void driver5()
+{
+ uint32_t b,c;
+ b=0, c=0, hashlittle2("", 0, &c, &b);
+ printf("hash is %.8lx %.8lx\n", c, b); /* deadbeef deadbeef */
+ b=0xdeadbeef, c=0, hashlittle2("", 0, &c, &b);
+ printf("hash is %.8lx %.8lx\n", c, b); /* bd5b7dde deadbeef */
+ b=0xdeadbeef, c=0xdeadbeef, hashlittle2("", 0, &c, &b);
+ printf("hash is %.8lx %.8lx\n", c, b); /* 9c093ccd bd5b7dde */
+ b=0, c=0, hashlittle2("Four score and seven years ago", 30, &c, &b);
+ printf("hash is %.8lx %.8lx\n", c, b); /* 17770551 ce7226e6 */
+ b=1, c=0, hashlittle2("Four score and seven years ago", 30, &c, &b);
+ printf("hash is %.8lx %.8lx\n", c, b); /* e3607cae bd371de4 */
+ b=0, c=1, hashlittle2("Four score and seven years ago", 30, &c, &b);
+ printf("hash is %.8lx %.8lx\n", c, b); /* cd628161 6cbea4b3 */
+ c = hashlittle("Four score and seven years ago", 30, 0);
+ printf("hash is %.8lx\n", c); /* 17770551 */
+ c = hashlittle("Four score and seven years ago", 30, 1);
+ printf("hash is %.8lx\n", c); /* cd628161 */
+}
+
+
+int main()
+{
+ driver1(); /* test that the key is hashed: used for timings */
+ driver2(); /* test that whole key is hashed thoroughly */
+ driver3(); /* test that nothing but the key is hashed */
+ driver4(); /* test hashing multiple buffers (all buffers are null) */
+ driver5(); /* test the hash against known vectors */
+ return 1;
+}
+
+#endif /* SELF_TEST */
diff --git a/src/journal/lookup3.h b/src/journal/lookup3.h
new file mode 100644
index 0000000000..502b42c209
--- /dev/null
+++ b/src/journal/lookup3.h
@@ -0,0 +1,22 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+uint32_t jenkins_hashword(const uint32_t *k, size_t length, uint32_t initval);
+void jenkins_hashword2(const uint32_t *k, size_t length, uint32_t *pc, uint32_t *pb);
+
+uint32_t jenkins_hashlittle(const void *key, size_t length, uint32_t initval);
+void jenkins_hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb);
+
+uint32_t jenkins_hashbig(const void *key, size_t length, uint32_t initval);
+
+static inline uint64_t hash64(const void *data, size_t length) {
+ uint32_t a = 0, b = 0;
+
+ jenkins_hashlittle2(data, length, &a, &b);
+
+ return ((uint64_t) a << 32ULL) | (uint64_t) b;
+}
diff --git a/src/journal/mmap-cache.c b/src/journal/mmap-cache.c
new file mode 100644
index 0000000000..251aefe121
--- /dev/null
+++ b/src/journal/mmap-cache.c
@@ -0,0 +1,577 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <string.h>
+
+#include "hashmap.h"
+#include "list.h"
+#include "log.h"
+#include "util.h"
+#include "macro.h"
+#include "mmap-cache.h"
+
+typedef struct Window Window;
+typedef struct Context Context;
+typedef struct FileDescriptor FileDescriptor;
+
+struct Window {
+ MMapCache *cache;
+
+ bool keep_always;
+ bool in_unused;
+
+ void *ptr;
+ uint64_t offset;
+ int prot;
+ size_t size;
+
+ FileDescriptor *fd;
+
+ LIST_FIELDS(Window, by_fd);
+ LIST_FIELDS(Window, unused);
+
+ LIST_HEAD(Context, contexts);
+};
+
+struct Context {
+ MMapCache *cache;
+ unsigned id;
+ Window *window;
+
+ LIST_FIELDS(Context, by_window);
+};
+
+struct FileDescriptor {
+ MMapCache *cache;
+ int fd;
+ LIST_HEAD(Window, windows);
+};
+
+struct MMapCache {
+ int n_ref;
+
+ Hashmap *fds;
+ Hashmap *contexts;
+
+ unsigned n_windows;
+
+ LIST_HEAD(Window, unused);
+ Window *last_unused;
+};
+
+#define WINDOWS_MIN 64
+#define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
+
+MMapCache* mmap_cache_new(void) {
+ MMapCache *m;
+
+ m = new0(MMapCache, 1);
+ if (!m)
+ return NULL;
+
+ m->n_ref = 1;
+ return m;
+}
+
+MMapCache* mmap_cache_ref(MMapCache *m) {
+ assert(m);
+ assert(m->n_ref > 0);
+
+ m->n_ref ++;
+ return m;
+}
+
+static void window_unlink(Window *w) {
+ Context *c;
+
+ assert(w);
+
+ if (w->ptr)
+ munmap(w->ptr, w->size);
+
+ if (w->fd)
+ LIST_REMOVE(Window, by_fd, w->fd->windows, w);
+
+ if (w->in_unused) {
+ if (w->cache->last_unused == w)
+ w->cache->last_unused = w->unused_prev;
+
+ LIST_REMOVE(Window, unused, w->cache->unused, w);
+ }
+
+ LIST_FOREACH(by_window, c, w->contexts) {
+ assert(c->window == w);
+ c->window = NULL;
+ }
+}
+
+static void window_free(Window *w) {
+ assert(w);
+
+ window_unlink(w);
+ w->cache->n_windows--;
+ free(w);
+}
+
+static bool window_matches(Window *w, int fd, int prot, uint64_t offset, size_t size) {
+ assert(w);
+ assert(fd >= 0);
+ assert(size > 0);
+
+ return
+ w->fd &&
+ fd == w->fd->fd &&
+ prot == w->prot &&
+ offset >= w->offset &&
+ offset + size <= w->offset + w->size;
+}
+
+static Window *window_add(MMapCache *m) {
+ Window *w;
+
+ assert(m);
+
+ if (!m->last_unused || m->n_windows <= WINDOWS_MIN) {
+
+ /* Allocate a new window */
+ w = new0(Window, 1);
+ if (!w)
+ return NULL;
+ m->n_windows++;
+ } else {
+
+ /* Reuse an existing one */
+ w = m->last_unused;
+ window_unlink(w);
+ zero(*w);
+ }
+
+ w->cache = m;
+ return w;
+}
+
+static void context_detach_window(Context *c) {
+ Window *w;
+
+ assert(c);
+
+ if (!c->window)
+ return;
+
+ w = c->window;
+ c->window = NULL;
+ LIST_REMOVE(Context, by_window, w->contexts, c);
+
+ if (!w->contexts && !w->keep_always) {
+ /* Not used anymore? */
+ LIST_PREPEND(Window, unused, c->cache->unused, w);
+ if (!c->cache->last_unused)
+ c->cache->last_unused = w;
+
+ w->in_unused = true;
+ }
+}
+
+static void context_attach_window(Context *c, Window *w) {
+ assert(c);
+ assert(w);
+
+ if (c->window == w)
+ return;
+
+ context_detach_window(c);
+
+ if (w->in_unused) {
+ /* Used again? */
+ LIST_REMOVE(Window, unused, c->cache->unused, w);
+ if (c->cache->last_unused == w)
+ c->cache->last_unused = w->unused_prev;
+
+ w->in_unused = false;
+ }
+
+ c->window = w;
+ LIST_PREPEND(Context, by_window, w->contexts, c);
+}
+
+static Context *context_add(MMapCache *m, unsigned id) {
+ Context *c;
+ int r;
+
+ assert(m);
+
+ c = hashmap_get(m->contexts, UINT_TO_PTR(id + 1));
+ if (c)
+ return c;
+
+ r = hashmap_ensure_allocated(&m->contexts, trivial_hash_func, trivial_compare_func);
+ if (r < 0)
+ return NULL;
+
+ c = new0(Context, 1);
+ if (!c)
+ return NULL;
+
+ c->cache = m;
+ c->id = id;
+
+ r = hashmap_put(m->contexts, UINT_TO_PTR(id + 1), c);
+ if (r < 0) {
+ free(c);
+ return NULL;
+ }
+
+ return c;
+}
+
+static void context_free(Context *c) {
+ assert(c);
+
+ context_detach_window(c);
+
+ if (c->cache)
+ assert_se(hashmap_remove(c->cache->contexts, UINT_TO_PTR(c->id + 1)));
+
+ free(c);
+}
+
+static void fd_free(FileDescriptor *f) {
+ assert(f);
+
+ while (f->windows)
+ window_free(f->windows);
+
+ if (f->cache)
+ assert_se(hashmap_remove(f->cache->fds, INT_TO_PTR(f->fd + 1)));
+
+ free(f);
+}
+
+static FileDescriptor* fd_add(MMapCache *m, int fd) {
+ FileDescriptor *f;
+ int r;
+
+ assert(m);
+ assert(fd >= 0);
+
+ f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
+ if (f)
+ return f;
+
+ r = hashmap_ensure_allocated(&m->fds, trivial_hash_func, trivial_compare_func);
+ if (r < 0)
+ return NULL;
+
+ f = new0(FileDescriptor, 1);
+ if (!f)
+ return NULL;
+
+ f->cache = m;
+ f->fd = fd;
+
+ r = hashmap_put(m->fds, UINT_TO_PTR(fd + 1), f);
+ if (r < 0) {
+ free(f);
+ return NULL;
+ }
+
+ return f;
+}
+
+static void mmap_cache_free(MMapCache *m) {
+ Context *c;
+ FileDescriptor *f;
+
+ assert(m);
+
+ while ((c = hashmap_first(m->contexts)))
+ context_free(c);
+
+ while ((f = hashmap_first(m->fds)))
+ fd_free(f);
+
+ while (m->unused)
+ window_free(m->unused);
+
+ free(m);
+}
+
+MMapCache* mmap_cache_unref(MMapCache *m) {
+ assert(m);
+ assert(m->n_ref > 0);
+
+ m->n_ref --;
+ if (m->n_ref == 0)
+ mmap_cache_free(m);
+
+ return NULL;
+}
+
+static int make_room(MMapCache *m) {
+ assert(m);
+
+ if (!m->last_unused)
+ return 0;
+
+ window_free(m->last_unused);
+ return 1;
+}
+
+static int try_context(
+ MMapCache *m,
+ int fd,
+ int prot,
+ unsigned context,
+ bool keep_always,
+ uint64_t offset,
+ size_t size,
+ void **ret) {
+
+ Context *c;
+
+ assert(m);
+ assert(m->n_ref > 0);
+ assert(fd >= 0);
+ assert(size > 0);
+ assert(ret);
+
+ c = hashmap_get(m->contexts, UINT_TO_PTR(context+1));
+ if (!c)
+ return 0;
+
+ assert(c->id == context);
+
+ if (!c->window)
+ return 0;
+
+ if (!window_matches(c->window, fd, prot, offset, size)) {
+
+ /* Drop the reference to the window, since it's unnecessary now */
+ context_detach_window(c);
+ return 0;
+ }
+
+ c->window->keep_always = c->window->keep_always || keep_always;
+
+ *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
+ return 1;
+}
+
+static int find_mmap(
+ MMapCache *m,
+ int fd,
+ int prot,
+ unsigned context,
+ bool keep_always,
+ uint64_t offset,
+ size_t size,
+ void **ret) {
+
+ FileDescriptor *f;
+ Window *w;
+ Context *c;
+
+ assert(m);
+ assert(m->n_ref > 0);
+ assert(fd >= 0);
+ assert(size > 0);
+ assert(ret);
+
+ f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
+ if (!f)
+ return 0;
+
+ assert(f->fd == fd);
+
+ LIST_FOREACH(by_fd, w, f->windows)
+ if (window_matches(w, fd, prot, offset, size))
+ break;
+
+ if (!w)
+ return 0;
+
+ c = context_add(m, context);
+ if (!c)
+ return -ENOMEM;
+
+ context_attach_window(c, w);
+ w->keep_always = w->keep_always || keep_always;
+
+ *ret = (uint8_t*) w->ptr + (offset - w->offset);
+ return 1;
+}
+
+static int add_mmap(
+ MMapCache *m,
+ int fd,
+ int prot,
+ unsigned context,
+ bool keep_always,
+ uint64_t offset,
+ size_t size,
+ struct stat *st,
+ void **ret) {
+
+ uint64_t woffset, wsize;
+ Context *c;
+ FileDescriptor *f;
+ Window *w;
+ void *d;
+ int r;
+
+ assert(m);
+ assert(m->n_ref > 0);
+ assert(fd >= 0);
+ assert(size > 0);
+ assert(ret);
+
+ woffset = offset & ~((uint64_t) page_size() - 1ULL);
+ wsize = size + (offset - woffset);
+ wsize = PAGE_ALIGN(wsize);
+
+ if (wsize < WINDOW_SIZE) {
+ uint64_t delta;
+
+ delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
+
+ if (delta > offset)
+ woffset = 0;
+ else
+ woffset -= delta;
+
+ wsize = WINDOW_SIZE;
+ }
+
+ if (st) {
+ /* Memory maps that are larger then the files
+ underneath have undefined behavior. Hence, clamp
+ things to the file size if we know it */
+
+ if (woffset >= (uint64_t) st->st_size)
+ return -EADDRNOTAVAIL;
+
+ if (woffset + wsize > (uint64_t) st->st_size)
+ wsize = PAGE_ALIGN(st->st_size - woffset);
+ }
+
+ for (;;) {
+ d = mmap(NULL, wsize, prot, MAP_SHARED, fd, woffset);
+ if (d != MAP_FAILED)
+ break;
+ if (errno != ENOMEM)
+ return -errno;
+
+ r = make_room(m);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENOMEM;
+ }
+
+ c = context_add(m, context);
+ if (!c)
+ return -ENOMEM;
+
+ f = fd_add(m, fd);
+ if (!f)
+ return -ENOMEM;
+
+ w = window_add(m);
+ if (!w)
+ return -ENOMEM;
+
+ w->keep_always = keep_always;
+ w->ptr = d;
+ w->offset = woffset;
+ w->prot = prot;
+ w->size = wsize;
+ w->fd = f;
+
+ LIST_PREPEND(Window, by_fd, f->windows, w);
+
+ context_detach_window(c);
+ c->window = w;
+ LIST_PREPEND(Context, by_window, w->contexts, c);
+
+ *ret = (uint8_t*) w->ptr + (offset - w->offset);
+ return 1;
+}
+
+int mmap_cache_get(
+ MMapCache *m,
+ int fd,
+ int prot,
+ unsigned context,
+ bool keep_always,
+ uint64_t offset,
+ size_t size,
+ struct stat *st,
+ void **ret) {
+
+ int r;
+
+ assert(m);
+ assert(m->n_ref > 0);
+ assert(fd >= 0);
+ assert(size > 0);
+ assert(ret);
+
+ /* Check whether the current context is the right one already */
+ r = try_context(m, fd, prot, context, keep_always, offset, size, ret);
+ if (r != 0)
+ return r;
+
+ /* Search for a matching mmap */
+ r = find_mmap(m, fd, prot, context, keep_always, offset, size, ret);
+ if (r != 0)
+ return r;
+
+ /* Create a new mmap */
+ return add_mmap(m, fd, prot, context, keep_always, offset, size, st, ret);
+}
+
+void mmap_cache_close_fd(MMapCache *m, int fd) {
+ FileDescriptor *f;
+
+ assert(m);
+ assert(fd >= 0);
+
+ f = hashmap_get(m->fds, INT_TO_PTR(fd + 1));
+ if (!f)
+ return;
+
+ fd_free(f);
+}
+
+void mmap_cache_close_context(MMapCache *m, unsigned context) {
+ Context *c;
+
+ assert(m);
+
+ c = hashmap_get(m->contexts, UINT_TO_PTR(context + 1));
+ if (!c)
+ return;
+
+ context_free(c);
+}
diff --git a/src/journal/mmap-cache.h b/src/journal/mmap-cache.h
new file mode 100644
index 0000000000..0c42fb88e6
--- /dev/null
+++ b/src/journal/mmap-cache.h
@@ -0,0 +1,36 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+
+typedef struct MMapCache MMapCache;
+
+MMapCache* mmap_cache_new(void);
+MMapCache* mmap_cache_ref(MMapCache *m);
+MMapCache* mmap_cache_unref(MMapCache *m);
+
+int mmap_cache_get(MMapCache *m, int fd, int prot, unsigned context, bool keep_always, uint64_t offset, size_t size, struct stat *st, void **ret);
+void mmap_cache_close_fd(MMapCache *m, int fd);
+void mmap_cache_close_context(MMapCache *m, unsigned context);
diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c
new file mode 100644
index 0000000000..a346691e21
--- /dev/null
+++ b/src/journal/sd-journal.c
@@ -0,0 +1,2391 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <sys/vfs.h>
+#include <linux/magic.h>
+
+#include "sd-journal.h"
+#include "journal-def.h"
+#include "journal-file.h"
+#include "hashmap.h"
+#include "list.h"
+#include "path-util.h"
+#include "lookup3.h"
+#include "compress.h"
+#include "journal-internal.h"
+#include "missing.h"
+
+#define JOURNAL_FILES_MAX 1024
+
+#define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC)
+
+static void detach_location(sd_journal *j) {
+ Iterator i;
+ JournalFile *f;
+
+ assert(j);
+
+ j->current_file = NULL;
+ j->current_field = 0;
+
+ HASHMAP_FOREACH(f, j->files, i)
+ f->current_offset = 0;
+}
+
+static void reset_location(sd_journal *j) {
+ assert(j);
+
+ detach_location(j);
+ zero(j->current_location);
+}
+
+static void init_location(Location *l, LocationType type, JournalFile *f, Object *o) {
+ assert(l);
+ assert(type == LOCATION_DISCRETE || type == LOCATION_SEEK);
+ assert(f);
+ assert(o->object.type == OBJECT_ENTRY);
+
+ l->type = type;
+ l->seqnum = le64toh(o->entry.seqnum);
+ l->seqnum_id = f->header->seqnum_id;
+ l->realtime = le64toh(o->entry.realtime);
+ l->monotonic = le64toh(o->entry.monotonic);
+ l->boot_id = o->entry.boot_id;
+ l->xor_hash = le64toh(o->entry.xor_hash);
+
+ l->seqnum_set = l->realtime_set = l->monotonic_set = l->xor_hash_set = true;
+}
+
+static void set_location(sd_journal *j, LocationType type, JournalFile *f, Object *o, uint64_t offset) {
+ assert(j);
+ assert(type == LOCATION_DISCRETE || type == LOCATION_SEEK);
+ assert(f);
+ assert(o);
+
+ init_location(&j->current_location, type, f, o);
+
+ j->current_file = f;
+ j->current_field = 0;
+
+ f->current_offset = offset;
+}
+
+static int match_is_valid(const void *data, size_t size) {
+ const char *b, *p;
+
+ assert(data);
+
+ if (size < 2)
+ return false;
+
+ if (startswith(data, "__"))
+ return false;
+
+ b = data;
+ for (p = b; p < b + size; p++) {
+
+ if (*p == '=')
+ return p > b;
+
+ if (*p == '_')
+ continue;
+
+ if (*p >= 'A' && *p <= 'Z')
+ continue;
+
+ if (*p >= '0' && *p <= '9')
+ continue;
+
+ return false;
+ }
+
+ return false;
+}
+
+static bool same_field(const void *_a, size_t s, const void *_b, size_t t) {
+ const uint8_t *a = _a, *b = _b;
+ size_t j;
+
+ for (j = 0; j < s && j < t; j++) {
+
+ if (a[j] != b[j])
+ return false;
+
+ if (a[j] == '=')
+ return true;
+ }
+
+ return true;
+}
+
+static Match *match_new(Match *p, MatchType t) {
+ Match *m;
+
+ m = new0(Match, 1);
+ if (!m)
+ return NULL;
+
+ m->type = t;
+
+ if (p) {
+ m->parent = p;
+ LIST_PREPEND(Match, matches, p->matches, m);
+ }
+
+ return m;
+}
+
+static void match_free(Match *m) {
+ assert(m);
+
+ while (m->matches)
+ match_free(m->matches);
+
+ if (m->parent)
+ LIST_REMOVE(Match, matches, m->parent->matches, m);
+
+ free(m->data);
+ free(m);
+}
+
+static void match_free_if_empty(Match *m) {
+ assert(m);
+
+ if (m->matches)
+ return;
+
+ match_free(m);
+}
+
+_public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) {
+ Match *l2, *l3, *add_here = NULL, *m;
+ le64_t le_hash;
+
+ if (!j)
+ return -EINVAL;
+
+ if (!data)
+ return -EINVAL;
+
+ if (size == 0)
+ size = strlen(data);
+
+ if (!match_is_valid(data, size))
+ return -EINVAL;
+
+ /* level 0: OR term
+ * level 1: AND terms
+ * level 2: OR terms
+ * level 3: concrete matches */
+
+ if (!j->level0) {
+ j->level0 = match_new(NULL, MATCH_OR_TERM);
+ if (!j->level0)
+ return -ENOMEM;
+ }
+
+ if (!j->level1) {
+ j->level1 = match_new(j->level0, MATCH_AND_TERM);
+ if (!j->level1)
+ return -ENOMEM;
+ }
+
+ assert(j->level0->type == MATCH_OR_TERM);
+ assert(j->level1->type == MATCH_AND_TERM);
+
+ le_hash = htole64(hash64(data, size));
+
+ LIST_FOREACH(matches, l2, j->level1->matches) {
+ assert(l2->type == MATCH_OR_TERM);
+
+ LIST_FOREACH(matches, l3, l2->matches) {
+ assert(l3->type == MATCH_DISCRETE);
+
+ /* Exactly the same match already? Then ignore
+ * this addition */
+ if (l3->le_hash == le_hash &&
+ l3->size == size &&
+ memcmp(l3->data, data, size) == 0)
+ return 0;
+
+ /* Same field? Then let's add this to this OR term */
+ if (same_field(data, size, l3->data, l3->size)) {
+ add_here = l2;
+ break;
+ }
+ }
+
+ if (add_here)
+ break;
+ }
+
+ if (!add_here) {
+ add_here = match_new(j->level1, MATCH_OR_TERM);
+ if (!add_here)
+ goto fail;
+ }
+
+ m = match_new(add_here, MATCH_DISCRETE);
+ if (!m)
+ goto fail;
+
+ m->le_hash = le_hash;
+ m->size = size;
+ m->data = memdup(data, size);
+ if (!m->data)
+ goto fail;
+
+ detach_location(j);
+
+ return 0;
+
+fail:
+ if (add_here)
+ match_free_if_empty(add_here);
+
+ if (j->level1)
+ match_free_if_empty(j->level1);
+
+ if (j->level0)
+ match_free_if_empty(j->level0);
+
+ return -ENOMEM;
+}
+
+_public_ int sd_journal_add_disjunction(sd_journal *j) {
+ Match *m;
+
+ assert(j);
+
+ if (!j->level0)
+ return 0;
+
+ if (!j->level1)
+ return 0;
+
+ if (!j->level1->matches)
+ return 0;
+
+ m = match_new(j->level0, MATCH_AND_TERM);
+ if (!m)
+ return -ENOMEM;
+
+ j->level1 = m;
+ return 0;
+}
+
+static char *match_make_string(Match *m) {
+ char *p, *r;
+ Match *i;
+ bool enclose = false;
+
+ if (!m)
+ return strdup("");
+
+ if (m->type == MATCH_DISCRETE)
+ return strndup(m->data, m->size);
+
+ p = NULL;
+ LIST_FOREACH(matches, i, m->matches) {
+ char *t, *k;
+
+ t = match_make_string(i);
+ if (!t) {
+ free(p);
+ return NULL;
+ }
+
+ if (p) {
+ k = strjoin(p, m->type == MATCH_OR_TERM ? " OR " : " AND ", t, NULL);
+ free(p);
+ free(t);
+
+ if (!k)
+ return NULL;
+
+ p = k;
+
+ enclose = true;
+ } else {
+ free(p);
+ p = t;
+ }
+ }
+
+ if (enclose) {
+ r = strjoin("(", p, ")", NULL);
+ free(p);
+ return r;
+ }
+
+ return p;
+}
+
+char *journal_make_match_string(sd_journal *j) {
+ assert(j);
+
+ return match_make_string(j->level0);
+}
+
+_public_ void sd_journal_flush_matches(sd_journal *j) {
+
+ if (!j)
+ return;
+
+ if (j->level0)
+ match_free(j->level0);
+
+ j->level0 = j->level1 = NULL;
+
+ detach_location(j);
+}
+
+static int compare_entry_order(JournalFile *af, Object *_ao,
+ JournalFile *bf, uint64_t bp) {
+
+ uint64_t a, b;
+ Object *ao, *bo;
+ int r;
+
+ assert(af);
+ assert(bf);
+ assert(_ao);
+
+ /* The mmap cache might invalidate the object from the first
+ * file if we look at the one from the second file. Hence
+ * temporarily copy the header of the first one, and look at
+ * that only. */
+ ao = alloca(offsetof(EntryObject, items));
+ memcpy(ao, _ao, offsetof(EntryObject, items));
+
+ r = journal_file_move_to_object(bf, OBJECT_ENTRY, bp, &bo);
+ if (r < 0)
+ return strcmp(af->path, bf->path);
+
+ /* We operate on two different files here, hence we can access
+ * two objects at the same time, which we normally can't.
+ *
+ * If contents and timestamps match, these entries are
+ * identical, even if the seqnum does not match */
+
+ if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id) &&
+ ao->entry.monotonic == bo->entry.monotonic &&
+ ao->entry.realtime == bo->entry.realtime &&
+ ao->entry.xor_hash == bo->entry.xor_hash)
+ return 0;
+
+ if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
+
+ /* If this is from the same seqnum source, compare
+ * seqnums */
+ a = le64toh(ao->entry.seqnum);
+ b = le64toh(bo->entry.seqnum);
+
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+
+ /* Wow! This is weird, different data but the same
+ * seqnums? Something is borked, but let's make the
+ * best of it and compare by time. */
+ }
+
+ if (sd_id128_equal(ao->entry.boot_id, bo->entry.boot_id)) {
+
+ /* If the boot id matches compare monotonic time */
+ a = le64toh(ao->entry.monotonic);
+ b = le64toh(bo->entry.monotonic);
+
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ }
+
+ /* Otherwise compare UTC time */
+ a = le64toh(ao->entry.realtime);
+ b = le64toh(bo->entry.realtime);
+
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+
+ /* Finally, compare by contents */
+ a = le64toh(ao->entry.xor_hash);
+ b = le64toh(bo->entry.xor_hash);
+
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+
+ return 0;
+}
+
+static int compare_with_location(JournalFile *af, Object *ao, Location *l) {
+ uint64_t a;
+
+ assert(af);
+ assert(ao);
+ assert(l);
+ assert(l->type == LOCATION_DISCRETE || l->type == LOCATION_SEEK);
+
+ if (l->monotonic_set &&
+ sd_id128_equal(ao->entry.boot_id, l->boot_id) &&
+ l->realtime_set &&
+ le64toh(ao->entry.realtime) == l->realtime &&
+ l->xor_hash_set &&
+ le64toh(ao->entry.xor_hash) == l->xor_hash)
+ return 0;
+
+ if (l->seqnum_set &&
+ sd_id128_equal(af->header->seqnum_id, l->seqnum_id)) {
+
+ a = le64toh(ao->entry.seqnum);
+
+ if (a < l->seqnum)
+ return -1;
+ if (a > l->seqnum)
+ return 1;
+ }
+
+ if (l->monotonic_set &&
+ sd_id128_equal(ao->entry.boot_id, l->boot_id)) {
+
+ a = le64toh(ao->entry.monotonic);
+
+ if (a < l->monotonic)
+ return -1;
+ if (a > l->monotonic)
+ return 1;
+ }
+
+ if (l->realtime_set) {
+
+ a = le64toh(ao->entry.realtime);
+
+ if (a < l->realtime)
+ return -1;
+ if (a > l->realtime)
+ return 1;
+ }
+
+ if (l->xor_hash_set) {
+ a = le64toh(ao->entry.xor_hash);
+
+ if (a < l->xor_hash)
+ return -1;
+ if (a > l->xor_hash)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int next_for_match(
+ sd_journal *j,
+ Match *m,
+ JournalFile *f,
+ uint64_t after_offset,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ int r;
+ uint64_t np = 0;
+ Object *n;
+
+ assert(j);
+ assert(m);
+ assert(f);
+
+ if (m->type == MATCH_DISCRETE) {
+ uint64_t dp;
+
+ r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp);
+ if (r <= 0)
+ return r;
+
+ return journal_file_move_to_entry_by_offset_for_data(f, dp, after_offset, direction, ret, offset);
+
+ } else if (m->type == MATCH_OR_TERM) {
+ Match *i;
+
+ /* Find the earliest match beyond after_offset */
+
+ LIST_FOREACH(matches, i, m->matches) {
+ uint64_t cp;
+
+ r = next_for_match(j, i, f, after_offset, direction, NULL, &cp);
+ if (r < 0)
+ return r;
+ else if (r > 0) {
+ if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp))
+ np = cp;
+ }
+ }
+
+ } else if (m->type == MATCH_AND_TERM) {
+ Match *i;
+ bool continue_looking;
+
+ /* Always jump to the next matching entry and repeat
+ * this until we fine and offset that matches for all
+ * matches. */
+
+ if (!m->matches)
+ return 0;
+
+ np = 0;
+ do {
+ continue_looking = false;
+
+ LIST_FOREACH(matches, i, m->matches) {
+ uint64_t cp, limit;
+
+ if (np == 0)
+ limit = after_offset;
+ else if (direction == DIRECTION_DOWN)
+ limit = MAX(np, after_offset);
+ else
+ limit = MIN(np, after_offset);
+
+ r = next_for_match(j, i, f, limit, direction, NULL, &cp);
+ if (r <= 0)
+ return r;
+
+ if ((direction == DIRECTION_DOWN ? cp >= after_offset : cp <= after_offset) &&
+ (np == 0 || (direction == DIRECTION_DOWN ? cp > np : np < cp))) {
+ np = cp;
+ continue_looking = true;
+ }
+ }
+
+ } while (continue_looking);
+ }
+
+ if (np == 0)
+ return 0;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = n;
+ if (offset)
+ *offset = np;
+
+ return 1;
+}
+
+static int find_location_for_match(
+ sd_journal *j,
+ Match *m,
+ JournalFile *f,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ int r;
+
+ assert(j);
+ assert(m);
+ assert(f);
+
+ if (m->type == MATCH_DISCRETE) {
+ uint64_t dp;
+
+ r = journal_file_find_data_object_with_hash(f, m->data, m->size, le64toh(m->le_hash), NULL, &dp);
+ if (r <= 0)
+ return r;
+
+ /* FIXME: missing: find by monotonic */
+
+ if (j->current_location.type == LOCATION_HEAD)
+ return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_DOWN, ret, offset);
+ if (j->current_location.type == LOCATION_TAIL)
+ return journal_file_next_entry_for_data(f, NULL, 0, dp, DIRECTION_UP, ret, offset);
+ if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
+ return journal_file_move_to_entry_by_seqnum_for_data(f, dp, j->current_location.seqnum, direction, ret, offset);
+ if (j->current_location.monotonic_set) {
+ r = journal_file_move_to_entry_by_monotonic_for_data(f, dp, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset);
+ if (r != -ENOENT)
+ return r;
+ }
+ if (j->current_location.realtime_set)
+ return journal_file_move_to_entry_by_realtime_for_data(f, dp, j->current_location.realtime, direction, ret, offset);
+
+ return journal_file_next_entry_for_data(f, NULL, 0, dp, direction, ret, offset);
+
+ } else if (m->type == MATCH_OR_TERM) {
+ uint64_t np = 0;
+ Object *n;
+ Match *i;
+
+ /* Find the earliest match */
+
+ LIST_FOREACH(matches, i, m->matches) {
+ uint64_t cp;
+
+ r = find_location_for_match(j, i, f, direction, NULL, &cp);
+ if (r < 0)
+ return r;
+ else if (r > 0) {
+ if (np == 0 || (direction == DIRECTION_DOWN ? np > cp : np < cp))
+ np = cp;
+ }
+ }
+
+ if (np == 0)
+ return 0;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, np, &n);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = n;
+ if (offset)
+ *offset = np;
+
+ return 1;
+
+ } else {
+ Match *i;
+ uint64_t np = 0;
+
+ assert(m->type == MATCH_AND_TERM);
+
+ /* First jump to the last match, and then find the
+ * next one where all matches match */
+
+ if (!m->matches)
+ return 0;
+
+ LIST_FOREACH(matches, i, m->matches) {
+ uint64_t cp;
+
+ r = find_location_for_match(j, i, f, direction, NULL, &cp);
+ if (r <= 0)
+ return r;
+
+ if (np == 0 || (direction == DIRECTION_DOWN ? np < cp : np > cp))
+ np = cp;
+ }
+
+ return next_for_match(j, m, f, np, direction, ret, offset);
+ }
+}
+
+static int find_location_with_matches(
+ sd_journal *j,
+ JournalFile *f,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ int r;
+
+ assert(j);
+ assert(f);
+ assert(ret);
+ assert(offset);
+
+ if (!j->level0) {
+ /* No matches is simple */
+
+ if (j->current_location.type == LOCATION_HEAD)
+ return journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, ret, offset);
+ if (j->current_location.type == LOCATION_TAIL)
+ return journal_file_next_entry(f, NULL, 0, DIRECTION_UP, ret, offset);
+ if (j->current_location.seqnum_set && sd_id128_equal(j->current_location.seqnum_id, f->header->seqnum_id))
+ return journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, ret, offset);
+ if (j->current_location.monotonic_set) {
+ r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset);
+ if (r != -ENOENT)
+ return r;
+ }
+ if (j->current_location.realtime_set)
+ return journal_file_move_to_entry_by_realtime(f, j->current_location.realtime, direction, ret, offset);
+
+ return journal_file_next_entry(f, NULL, 0, direction, ret, offset);
+ } else
+ return find_location_for_match(j, j->level0, f, direction, ret, offset);
+}
+
+static int next_with_matches(
+ sd_journal *j,
+ JournalFile *f,
+ direction_t direction,
+ Object **ret,
+ uint64_t *offset) {
+
+ Object *c;
+ uint64_t cp;
+
+ assert(j);
+ assert(f);
+ assert(ret);
+ assert(offset);
+
+ c = *ret;
+ cp = *offset;
+
+ /* No matches is easy. We simple advance the file
+ * pointer by one. */
+ if (!j->level0)
+ return journal_file_next_entry(f, c, cp, direction, ret, offset);
+
+ /* If we have a match then we look for the next matching entry
+ * with an offset at least one step larger */
+ return next_for_match(j, j->level0, f, direction == DIRECTION_DOWN ? cp+1 : cp-1, direction, ret, offset);
+}
+
+static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direction, Object **ret, uint64_t *offset) {
+ Object *c;
+ uint64_t cp;
+ int r;
+
+ assert(j);
+ assert(f);
+
+ if (f->current_offset > 0) {
+ cp = f->current_offset;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, cp, &c);
+ if (r < 0)
+ return r;
+
+ r = next_with_matches(j, f, direction, &c, &cp);
+ if (r <= 0)
+ return r;
+ } else {
+ r = find_location_with_matches(j, f, direction, &c, &cp);
+ if (r <= 0)
+ return r;
+ }
+
+ /* OK, we found the spot, now let's advance until to an entry
+ * that is actually different from what we were previously
+ * looking at. This is necessary to handle entries which exist
+ * in two (or more) journal files, and which shall all be
+ * suppressed but one. */
+
+ for (;;) {
+ bool found;
+
+ if (j->current_location.type == LOCATION_DISCRETE) {
+ int k;
+
+ k = compare_with_location(f, c, &j->current_location);
+ if (direction == DIRECTION_DOWN)
+ found = k > 0;
+ else
+ found = k < 0;
+ } else
+ found = true;
+
+ if (found) {
+ if (ret)
+ *ret = c;
+ if (offset)
+ *offset = cp;
+ return 1;
+ }
+
+ r = next_with_matches(j, f, direction, &c, &cp);
+ if (r <= 0)
+ return r;
+ }
+}
+
+static int real_journal_next(sd_journal *j, direction_t direction) {
+ JournalFile *f, *new_file = NULL;
+ uint64_t new_offset = 0;
+ Object *o;
+ uint64_t p;
+ Iterator i;
+ int r;
+
+ if (!j)
+ return -EINVAL;
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ bool found;
+
+ r = next_beyond_location(j, f, direction, &o, &p);
+ if (r < 0) {
+ log_debug("Can't iterate through %s, ignoring: %s", f->path, strerror(-r));
+ continue;
+ } else if (r == 0)
+ continue;
+
+ if (!new_file)
+ found = true;
+ else {
+ int k;
+
+ k = compare_entry_order(f, o, new_file, new_offset);
+
+ if (direction == DIRECTION_DOWN)
+ found = k < 0;
+ else
+ found = k > 0;
+ }
+
+ if (found) {
+ new_file = f;
+ new_offset = p;
+ }
+ }
+
+ if (!new_file)
+ return 0;
+
+ r = journal_file_move_to_object(new_file, OBJECT_ENTRY, new_offset, &o);
+ if (r < 0)
+ return r;
+
+ set_location(j, LOCATION_DISCRETE, new_file, o, new_offset);
+
+ return 1;
+}
+
+_public_ int sd_journal_next(sd_journal *j) {
+ return real_journal_next(j, DIRECTION_DOWN);
+}
+
+_public_ int sd_journal_previous(sd_journal *j) {
+ return real_journal_next(j, DIRECTION_UP);
+}
+
+static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) {
+ int c = 0, r;
+
+ if (!j)
+ return -EINVAL;
+
+ if (skip == 0) {
+ /* If this is not a discrete skip, then at least
+ * resolve the current location */
+ if (j->current_location.type != LOCATION_DISCRETE)
+ return real_journal_next(j, direction);
+
+ return 0;
+ }
+
+ do {
+ r = real_journal_next(j, direction);
+ if (r < 0)
+ return r;
+
+ if (r == 0)
+ return c;
+
+ skip--;
+ c++;
+ } while (skip > 0);
+
+ return c;
+}
+
+_public_ int sd_journal_next_skip(sd_journal *j, uint64_t skip) {
+ return real_journal_next_skip(j, DIRECTION_DOWN, skip);
+}
+
+_public_ int sd_journal_previous_skip(sd_journal *j, uint64_t skip) {
+ return real_journal_next_skip(j, DIRECTION_UP, skip);
+}
+
+_public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
+ Object *o;
+ int r;
+ char bid[33], sid[33];
+
+ if (!j)
+ return -EINVAL;
+ if (!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;
+
+ sd_id128_to_string(j->current_file->header->seqnum_id, sid);
+ sd_id128_to_string(o->entry.boot_id, bid);
+
+ if (asprintf(cursor,
+ "s=%s;i=%llx;b=%s;m=%llx;t=%llx;x=%llx",
+ sid, (unsigned long long) le64toh(o->entry.seqnum),
+ bid, (unsigned long long) le64toh(o->entry.monotonic),
+ (unsigned long long) le64toh(o->entry.realtime),
+ (unsigned long long) le64toh(o->entry.xor_hash)) < 0)
+ return -ENOMEM;
+
+ return 1;
+}
+
+_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
+ char *w, *state;
+ size_t l;
+ unsigned long long seqnum, monotonic, realtime, xor_hash;
+ bool
+ seqnum_id_set = false,
+ seqnum_set = false,
+ boot_id_set = false,
+ monotonic_set = false,
+ realtime_set = false,
+ xor_hash_set = false;
+ sd_id128_t seqnum_id, boot_id;
+
+ if (!j)
+ return -EINVAL;
+ if (isempty(cursor))
+ return -EINVAL;
+
+ FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
+ char *item;
+ int k = 0;
+
+ if (l < 2 || w[1] != '=')
+ return -EINVAL;
+
+ item = strndup(w, l);
+ if (!item)
+ return -ENOMEM;
+
+ switch (w[0]) {
+
+ case 's':
+ seqnum_id_set = true;
+ k = sd_id128_from_string(item+2, &seqnum_id);
+ break;
+
+ case 'i':
+ seqnum_set = true;
+ if (sscanf(item+2, "%llx", &seqnum) != 1)
+ k = -EINVAL;
+ break;
+
+ case 'b':
+ boot_id_set = true;
+ k = sd_id128_from_string(item+2, &boot_id);
+ break;
+
+ case 'm':
+ monotonic_set = true;
+ if (sscanf(item+2, "%llx", &monotonic) != 1)
+ k = -EINVAL;
+ break;
+
+ case 't':
+ realtime_set = true;
+ if (sscanf(item+2, "%llx", &realtime) != 1)
+ k = -EINVAL;
+ break;
+
+ case 'x':
+ xor_hash_set = true;
+ if (sscanf(item+2, "%llx", &xor_hash) != 1)
+ k = -EINVAL;
+ break;
+ }
+
+ free(item);
+
+ if (k < 0)
+ return k;
+ }
+
+ if ((!seqnum_set || !seqnum_id_set) &&
+ (!monotonic_set || !boot_id_set) &&
+ !realtime_set)
+ return -EINVAL;
+
+ reset_location(j);
+
+ j->current_location.type = LOCATION_SEEK;
+
+ if (realtime_set) {
+ j->current_location.realtime = (uint64_t) realtime;
+ j->current_location.realtime_set = true;
+ }
+
+ if (seqnum_set && seqnum_id_set) {
+ j->current_location.seqnum = (uint64_t) seqnum;
+ j->current_location.seqnum_id = seqnum_id;
+ j->current_location.seqnum_set = true;
+ }
+
+ if (monotonic_set && boot_id_set) {
+ j->current_location.monotonic = (uint64_t) monotonic;
+ j->current_location.boot_id = boot_id;
+ j->current_location.monotonic_set = true;
+ }
+
+ if (xor_hash_set) {
+ j->current_location.xor_hash = (uint64_t) xor_hash;
+ j->current_location.xor_hash_set = true;
+ }
+
+ 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;
+
+ reset_location(j);
+ j->current_location.type = LOCATION_SEEK;
+ j->current_location.boot_id = boot_id;
+ j->current_location.monotonic = usec;
+ j->current_location.monotonic_set = true;
+
+ return 0;
+}
+
+_public_ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec) {
+ if (!j)
+ return -EINVAL;
+
+ reset_location(j);
+ j->current_location.type = LOCATION_SEEK;
+ j->current_location.realtime = usec;
+ j->current_location.realtime_set = true;
+
+ return 0;
+}
+
+_public_ int sd_journal_seek_head(sd_journal *j) {
+ if (!j)
+ return -EINVAL;
+
+ reset_location(j);
+ j->current_location.type = LOCATION_HEAD;
+
+ return 0;
+}
+
+_public_ int sd_journal_seek_tail(sd_journal *j) {
+ if (!j)
+ return -EINVAL;
+
+ reset_location(j);
+ j->current_location.type = LOCATION_TAIL;
+
+ return 0;
+}
+
+static void check_network(sd_journal *j, int fd) {
+ struct statfs sfs;
+
+ assert(j);
+
+ if (j->on_network)
+ return;
+
+ if (fstatfs(fd, &sfs) < 0)
+ return;
+
+ j->on_network =
+ sfs.f_type == CIFS_MAGIC_NUMBER ||
+ sfs.f_type == CODA_SUPER_MAGIC ||
+ sfs.f_type == NCP_SUPER_MAGIC ||
+ sfs.f_type == NFS_SUPER_MAGIC ||
+ sfs.f_type == SMB_SUPER_MAGIC;
+}
+
+static int add_file(sd_journal *j, const char *prefix, const char *filename) {
+ char *path;
+ int r;
+ JournalFile *f;
+
+ assert(j);
+ assert(prefix);
+ assert(filename);
+
+ if ((j->flags & SD_JOURNAL_SYSTEM_ONLY) &&
+ !(streq(filename, "system.journal") ||
+ streq(filename, "system.journal~") ||
+ (startswith(filename, "system@") &&
+ (endswith(filename, ".journal") || endswith(filename, ".journal~")))))
+ return 0;
+
+ path = strjoin(prefix, "/", filename, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ if (hashmap_get(j->files, path)) {
+ free(path);
+ return 0;
+ }
+
+ if (hashmap_size(j->files) >= JOURNAL_FILES_MAX) {
+ log_debug("Too many open journal files, not adding %s, ignoring.", path);
+ free(path);
+ return 0;
+ }
+
+ r = journal_file_open(path, O_RDONLY, 0, false, false, NULL, j->mmap, NULL, &f);
+ free(path);
+
+ if (r < 0) {
+ if (errno == ENOENT)
+ return 0;
+
+ return r;
+ }
+
+ /* journal_file_dump(f); */
+
+ r = hashmap_put(j->files, f->path, f);
+ if (r < 0) {
+ journal_file_close(f);
+ return r;
+ }
+
+ check_network(j, f->fd);
+
+ j->current_invalidate_counter ++;
+
+ log_debug("File %s got added.", f->path);
+
+ return 0;
+}
+
+static int remove_file(sd_journal *j, const char *prefix, const char *filename) {
+ char *path;
+ JournalFile *f;
+
+ assert(j);
+ assert(prefix);
+ assert(filename);
+
+ path = strjoin(prefix, "/", filename, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ f = hashmap_get(j->files, path);
+ free(path);
+ if (!f)
+ return 0;
+
+ hashmap_remove(j->files, f->path);
+
+ log_debug("File %s got removed.", f->path);
+
+ if (j->current_file == f) {
+ j->current_file = NULL;
+ j->current_field = 0;
+ }
+
+ if (j->unique_file == f) {
+ j->unique_file = NULL;
+ j->unique_offset = 0;
+ }
+
+ journal_file_close(f);
+
+ j->current_invalidate_counter ++;
+
+ return 0;
+}
+
+static int add_directory(sd_journal *j, const char *prefix, const char *dirname) {
+ char *path;
+ int r;
+ DIR *d;
+ sd_id128_t id, mid;
+ Directory *m;
+
+ assert(j);
+ assert(prefix);
+ assert(dirname);
+
+ if ((j->flags & SD_JOURNAL_LOCAL_ONLY) &&
+ (sd_id128_from_string(dirname, &id) < 0 ||
+ sd_id128_get_machine(&mid) < 0 ||
+ !sd_id128_equal(id, mid)))
+ return 0;
+
+ path = strjoin(prefix, "/", dirname, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ d = opendir(path);
+ if (!d) {
+ log_debug("Failed to open %s: %m", path);
+ free(path);
+
+ if (errno == ENOENT)
+ return 0;
+ return -errno;
+ }
+
+ m = hashmap_get(j->directories_by_path, path);
+ if (!m) {
+ m = new0(Directory, 1);
+ if (!m) {
+ closedir(d);
+ free(path);
+ return -ENOMEM;
+ }
+
+ m->is_root = false;
+ m->path = path;
+
+ if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
+ closedir(d);
+ free(m->path);
+ free(m);
+ return -ENOMEM;
+ }
+
+ j->current_invalidate_counter ++;
+
+ log_debug("Directory %s got added.", m->path);
+
+ } else if (m->is_root) {
+ free (path);
+ closedir(d);
+ return 0;
+ } else
+ free(path);
+
+ if (m->wd <= 0 && j->inotify_fd >= 0) {
+
+ m->wd = inotify_add_watch(j->inotify_fd, m->path,
+ IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
+ IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT|IN_MOVED_FROM|
+ IN_ONLYDIR);
+
+ if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
+ inotify_rm_watch(j->inotify_fd, m->wd);
+ }
+
+ for (;;) {
+ struct dirent *de;
+ union dirent_storage buf;
+
+ r = readdir_r(d, &buf.de, &de);
+ if (r != 0 || !de)
+ break;
+
+ if (dirent_is_file_with_suffix(de, ".journal") ||
+ dirent_is_file_with_suffix(de, ".journal~")) {
+ r = add_file(j, m->path, de->d_name);
+ if (r < 0)
+ log_debug("Failed to add file %s/%s: %s", m->path, de->d_name, strerror(-r));
+ }
+ }
+
+ check_network(j, dirfd(d));
+
+ closedir(d);
+
+ return 0;
+}
+
+static int add_root_directory(sd_journal *j, const char *p) {
+ DIR *d;
+ Directory *m;
+ int r;
+
+ assert(j);
+ assert(p);
+
+ if ((j->flags & SD_JOURNAL_RUNTIME_ONLY) &&
+ !path_startswith(p, "/run"))
+ return -EINVAL;
+
+ d = opendir(p);
+ if (!d)
+ return -errno;
+
+ m = hashmap_get(j->directories_by_path, p);
+ if (!m) {
+ m = new0(Directory, 1);
+ if (!m) {
+ closedir(d);
+ return -ENOMEM;
+ }
+
+ m->is_root = true;
+ m->path = strdup(p);
+ if (!m->path) {
+ closedir(d);
+ free(m);
+ return -ENOMEM;
+ }
+
+ if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
+ closedir(d);
+ free(m->path);
+ free(m);
+ return -ENOMEM;
+ }
+
+ j->current_invalidate_counter ++;
+
+ log_debug("Root directory %s got added.", m->path);
+
+ } else if (!m->is_root) {
+ closedir(d);
+ return 0;
+ }
+
+ if (m->wd <= 0 && j->inotify_fd >= 0) {
+
+ m->wd = inotify_add_watch(j->inotify_fd, m->path,
+ IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB|IN_DELETE|
+ IN_ONLYDIR);
+
+ if (m->wd > 0 && hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m) < 0)
+ inotify_rm_watch(j->inotify_fd, m->wd);
+ }
+
+ for (;;) {
+ struct dirent *de;
+ union dirent_storage buf;
+ sd_id128_t id;
+
+ r = readdir_r(d, &buf.de, &de);
+ if (r != 0 || !de)
+ break;
+
+ if (dirent_is_file_with_suffix(de, ".journal") ||
+ dirent_is_file_with_suffix(de, ".journal~")) {
+ r = add_file(j, m->path, de->d_name);
+ if (r < 0)
+ log_debug("Failed to add file %s/%s: %s", m->path, de->d_name, strerror(-r));
+
+ } else if ((de->d_type == DT_DIR || de->d_type == DT_LNK || de->d_type == DT_UNKNOWN) &&
+ sd_id128_from_string(de->d_name, &id) >= 0) {
+
+ r = add_directory(j, m->path, de->d_name);
+ if (r < 0)
+ log_debug("Failed to add directory %s/%s: %s", m->path, de->d_name, strerror(-r));
+ }
+ }
+
+ check_network(j, dirfd(d));
+
+ closedir(d);
+
+ return 0;
+}
+
+static int remove_directory(sd_journal *j, Directory *d) {
+ assert(j);
+
+ if (d->wd > 0) {
+ hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd));
+
+ if (j->inotify_fd >= 0)
+ inotify_rm_watch(j->inotify_fd, d->wd);
+ }
+
+ hashmap_remove(j->directories_by_path, d->path);
+
+ if (d->is_root)
+ log_debug("Root directory %s got removed.", d->path);
+ else
+ log_debug("Directory %s got removed.", d->path);
+
+ free(d->path);
+ free(d);
+
+ return 0;
+}
+
+static int add_search_paths(sd_journal *j) {
+
+ const char search_paths[] =
+ "/run/log/journal\0"
+ "/var/log/journal\0";
+ const char *p;
+
+ assert(j);
+
+ /* We ignore most errors here, since the idea is to only open
+ * what's actually accessible, and ignore the rest. */
+
+ NULSTR_FOREACH(p, search_paths)
+ add_root_directory(j, p);
+
+ return 0;
+}
+
+static int allocate_inotify(sd_journal *j) {
+ assert(j);
+
+ if (j->inotify_fd < 0) {
+ j->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+ if (j->inotify_fd < 0)
+ return -errno;
+ }
+
+ if (!j->directories_by_wd) {
+ j->directories_by_wd = hashmap_new(trivial_hash_func, trivial_compare_func);
+ if (!j->directories_by_wd)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static sd_journal *journal_new(int flags, const char *path) {
+ sd_journal *j;
+
+ j = new0(sd_journal, 1);
+ if (!j)
+ return NULL;
+
+ j->inotify_fd = -1;
+ j->flags = flags;
+
+ if (path) {
+ j->path = strdup(path);
+ if (!j->path) {
+ free(j);
+ return NULL;
+ }
+ }
+
+ j->files = hashmap_new(string_hash_func, string_compare_func);
+ if (!j->files) {
+ free(j->path);
+ free(j);
+ return NULL;
+ }
+
+ j->directories_by_path = hashmap_new(string_hash_func, string_compare_func);
+ if (!j->directories_by_path) {
+ hashmap_free(j->files);
+ free(j->path);
+ free(j);
+ return NULL;
+ }
+
+ j->mmap = mmap_cache_new();
+ if (!j->mmap) {
+ hashmap_free(j->files);
+ hashmap_free(j->directories_by_path);
+ free(j->path);
+ free(j);
+ return NULL;
+ }
+
+ return j;
+}
+
+_public_ int sd_journal_open(sd_journal **ret, int flags) {
+ sd_journal *j;
+ int r;
+
+ if (!ret)
+ return -EINVAL;
+
+ if (flags & ~(SD_JOURNAL_LOCAL_ONLY|
+ SD_JOURNAL_RUNTIME_ONLY|
+ SD_JOURNAL_SYSTEM_ONLY))
+ return -EINVAL;
+
+ j = journal_new(flags, NULL);
+ if (!j)
+ return -ENOMEM;
+
+ r = add_search_paths(j);
+ if (r < 0)
+ goto fail;
+
+ *ret = j;
+ return 0;
+
+fail:
+ sd_journal_close(j);
+
+ return r;
+}
+
+_public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) {
+ sd_journal *j;
+ int r;
+
+ if (!ret)
+ return -EINVAL;
+
+ if (!path || !path_is_absolute(path))
+ return -EINVAL;
+
+ if (flags != 0)
+ return -EINVAL;
+
+ j = journal_new(flags, path);
+ if (!j)
+ return -ENOMEM;
+
+ r = add_root_directory(j, path);
+ if (r < 0)
+ goto fail;
+
+ *ret = j;
+ return 0;
+
+fail:
+ sd_journal_close(j);
+
+ return r;
+}
+
+_public_ void sd_journal_close(sd_journal *j) {
+ Directory *d;
+ JournalFile *f;
+
+ if (!j)
+ return;
+
+ while ((f = hashmap_steal_first(j->files)))
+ journal_file_close(f);
+
+ hashmap_free(j->files);
+
+ while ((d = hashmap_first(j->directories_by_path)))
+ remove_directory(j, d);
+
+ while ((d = hashmap_first(j->directories_by_wd)))
+ remove_directory(j, d);
+
+ hashmap_free(j->directories_by_path);
+ hashmap_free(j->directories_by_wd);
+
+ if (j->inotify_fd >= 0)
+ close_nointr_nofail(j->inotify_fd);
+
+ sd_journal_flush_matches(j);
+
+ if (j->mmap)
+ mmap_cache_unref(j->mmap);
+
+ free(j->path);
+ free(j->unique_field);
+ free(j);
+}
+
+_public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) {
+ Object *o;
+ JournalFile *f;
+ int r;
+
+ if (!j)
+ return -EINVAL;
+ if (!ret)
+ return -EINVAL;
+
+ f = j->current_file;
+ if (!f)
+ return -EADDRNOTAVAIL;
+
+ if (f->current_offset <= 0)
+ return -EADDRNOTAVAIL;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+ if (r < 0)
+ return r;
+
+ *ret = le64toh(o->entry.realtime);
+ return 0;
+}
+
+_public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id) {
+ Object *o;
+ JournalFile *f;
+ int r;
+ sd_id128_t id;
+
+ if (!j)
+ return -EINVAL;
+
+ f = j->current_file;
+ if (!f)
+ return -EADDRNOTAVAIL;
+
+ if (f->current_offset <= 0)
+ return -EADDRNOTAVAIL;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+ if (r < 0)
+ return r;
+
+ if (ret_boot_id)
+ *ret_boot_id = o->entry.boot_id;
+ else {
+ r = sd_id128_get_boot(&id);
+ if (r < 0)
+ return r;
+
+ if (!sd_id128_equal(id, o->entry.boot_id))
+ return -ESTALE;
+ }
+
+ if (ret)
+ *ret = le64toh(o->entry.monotonic);
+
+ return 0;
+}
+
+static bool field_is_valid(const char *field) {
+ const char *p;
+
+ assert(field);
+
+ if (isempty(field))
+ return false;
+
+ if (startswith(field, "__"))
+ return false;
+
+ for (p = field; *p; p++) {
+
+ if (*p == '_')
+ continue;
+
+ if (*p >= 'A' && *p <= 'Z')
+ continue;
+
+ if (*p >= '0' && *p <= '9')
+ continue;
+
+ return false;
+ }
+
+ return true;
+}
+
+_public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *size) {
+ JournalFile *f;
+ uint64_t i, n;
+ size_t field_length;
+ int r;
+ Object *o;
+
+ if (!j)
+ return -EINVAL;
+ if (!field)
+ return -EINVAL;
+ if (!data)
+ return -EINVAL;
+ if (!size)
+ return -EINVAL;
+
+ if (!field_is_valid(field))
+ return -EINVAL;
+
+ f = j->current_file;
+ if (!f)
+ return -EADDRNOTAVAIL;
+
+ if (f->current_offset <= 0)
+ return -EADDRNOTAVAIL;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+ if (r < 0)
+ return r;
+
+ field_length = strlen(field);
+
+ n = journal_file_entry_n_items(o);
+ for (i = 0; i < n; i++) {
+ uint64_t p, l;
+ le64_t le_hash;
+ size_t t;
+
+ p = le64toh(o->entry.items[i].object_offset);
+ le_hash = o->entry.items[i].hash;
+ r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+ if (r < 0)
+ return r;
+
+ if (le_hash != o->data.hash)
+ return -EBADMSG;
+
+ l = le64toh(o->object.size) - offsetof(Object, data.payload);
+
+ if (o->object.flags & OBJECT_COMPRESSED) {
+
+#ifdef HAVE_XZ
+ if (uncompress_startswith(o->data.payload, l,
+ &f->compress_buffer, &f->compress_buffer_size,
+ field, field_length, '=')) {
+
+ uint64_t rsize;
+
+ if (!uncompress_blob(o->data.payload, l,
+ &f->compress_buffer, &f->compress_buffer_size, &rsize))
+ return -EBADMSG;
+
+ *data = f->compress_buffer;
+ *size = (size_t) rsize;
+
+ return 0;
+ }
+#else
+ return -EPROTONOSUPPORT;
+#endif
+
+ } else if (l >= field_length+1 &&
+ memcmp(o->data.payload, field, field_length) == 0 &&
+ o->data.payload[field_length] == '=') {
+
+ t = (size_t) l;
+
+ if ((uint64_t) t != l)
+ return -E2BIG;
+
+ *data = o->data.payload;
+ *size = t;
+
+ return 0;
+ }
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+ if (r < 0)
+ return r;
+ }
+
+ return -ENOENT;
+}
+
+static int return_data(JournalFile *f, Object *o, const void **data, size_t *size) {
+ size_t t;
+ uint64_t l;
+
+ l = le64toh(o->object.size) - offsetof(Object, data.payload);
+ t = (size_t) l;
+
+ /* We can't read objects larger than 4G on a 32bit machine */
+ if ((uint64_t) t != l)
+ return -E2BIG;
+
+ if (o->object.flags & OBJECT_COMPRESSED) {
+#ifdef HAVE_XZ
+ uint64_t rsize;
+
+ if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
+ return -EBADMSG;
+
+ *data = f->compress_buffer;
+ *size = (size_t) rsize;
+#else
+ return -EPROTONOSUPPORT;
+#endif
+ } else {
+ *data = o->data.payload;
+ *size = t;
+ }
+
+ return 0;
+}
+
+_public_ int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *size) {
+ JournalFile *f;
+ uint64_t p, n;
+ le64_t le_hash;
+ int r;
+ Object *o;
+
+ if (!j)
+ return -EINVAL;
+ if (!data)
+ return -EINVAL;
+ if (!size)
+ return -EINVAL;
+
+ f = j->current_file;
+ if (!f)
+ return -EADDRNOTAVAIL;
+
+ if (f->current_offset <= 0)
+ return -EADDRNOTAVAIL;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+ if (r < 0)
+ return r;
+
+ n = journal_file_entry_n_items(o);
+ if (j->current_field >= n)
+ return 0;
+
+ p = le64toh(o->entry.items[j->current_field].object_offset);
+ le_hash = o->entry.items[j->current_field].hash;
+ r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
+ if (r < 0)
+ return r;
+
+ if (le_hash != o->data.hash)
+ return -EBADMSG;
+
+ r = return_data(f, o, data, size);
+ if (r < 0)
+ return r;
+
+ j->current_field ++;
+
+ return 1;
+}
+
+_public_ void sd_journal_restart_data(sd_journal *j) {
+ if (!j)
+ return;
+
+ j->current_field = 0;
+}
+
+_public_ int sd_journal_get_fd(sd_journal *j) {
+ int r;
+
+ if (!j)
+ return -EINVAL;
+
+ if (j->inotify_fd >= 0)
+ return j->inotify_fd;
+
+ r = allocate_inotify(j);
+ if (r < 0)
+ return r;
+
+ /* Iterate through all dirs again, to add them to the
+ * inotify */
+ if (j->path)
+ r = add_root_directory(j, j->path);
+ else
+ r = add_search_paths(j);
+ if (r < 0)
+ return r;
+
+ return j->inotify_fd;
+}
+
+static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
+ Directory *d;
+ int r;
+
+ assert(j);
+ assert(e);
+
+ /* Is this a subdirectory we watch? */
+ d = hashmap_get(j->directories_by_wd, INT_TO_PTR(e->wd));
+ if (d) {
+ sd_id128_t id;
+
+ if (!(e->mask & IN_ISDIR) && e->len > 0 &&
+ (endswith(e->name, ".journal") ||
+ endswith(e->name, ".journal~"))) {
+
+ /* Event for a journal file */
+
+ if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
+ r = add_file(j, d->path, e->name);
+ if (r < 0)
+ log_debug("Failed to add file %s/%s: %s", d->path, e->name, strerror(-r));
+
+ } else if (e->mask & (IN_DELETE|IN_MOVED_FROM|IN_UNMOUNT)) {
+
+ r = remove_file(j, d->path, e->name);
+ if (r < 0)
+ log_debug("Failed to remove file %s/%s: %s", d->path, e->name, strerror(-r));
+ }
+
+ } else if (!d->is_root && e->len == 0) {
+
+ /* Event for a subdirectory */
+
+ if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) {
+ r = remove_directory(j, d);
+ if (r < 0)
+ log_debug("Failed to remove directory %s: %s", d->path, strerror(-r));
+ }
+
+
+ } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && sd_id128_from_string(e->name, &id) >= 0) {
+
+ /* Event for root directory */
+
+ if (e->mask & (IN_CREATE|IN_MOVED_TO|IN_MODIFY|IN_ATTRIB)) {
+ r = add_directory(j, d->path, e->name);
+ if (r < 0)
+ log_debug("Failed to add directory %s/%s: %s", d->path, e->name, strerror(-r));
+ }
+ }
+
+ return;
+ }
+
+ if (e->mask & IN_IGNORED)
+ return;
+
+ log_warning("Unknown inotify event.");
+}
+
+static int determine_change(sd_journal *j) {
+ bool b;
+
+ assert(j);
+
+ b = j->current_invalidate_counter != j->last_invalidate_counter;
+ j->last_invalidate_counter = j->current_invalidate_counter;
+
+ return b ? SD_JOURNAL_INVALIDATE : SD_JOURNAL_APPEND;
+}
+
+_public_ int sd_journal_process(sd_journal *j) {
+ uint8_t buffer[sizeof(struct inotify_event) + FILENAME_MAX] _alignas_(struct inotify_event);
+ bool got_something = false;
+
+ if (!j)
+ return -EINVAL;
+
+ for (;;) {
+ struct inotify_event *e;
+ ssize_t l;
+
+ l = read(j->inotify_fd, buffer, sizeof(buffer));
+ if (l < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return got_something ? determine_change(j) : SD_JOURNAL_NOP;
+
+ return -errno;
+ }
+
+ got_something = true;
+
+ e = (struct inotify_event*) buffer;
+ while (l > 0) {
+ size_t step;
+
+ process_inotify_event(j, e);
+
+ step = sizeof(struct inotify_event) + e->len;
+ assert(step <= (size_t) l);
+
+ e = (struct inotify_event*) ((uint8_t*) e + step);
+ l -= step;
+ }
+ }
+
+ return determine_change(j);
+}
+
+_public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) {
+ int r;
+
+ assert(j);
+
+ if (j->inotify_fd < 0) {
+
+ /* This is the first invocation, hence create the
+ * inotify watch */
+ r = sd_journal_get_fd(j);
+ if (r < 0)
+ return r;
+
+ /* The journal might have changed since the context
+ * object was created and we weren't watching before,
+ * hence don't wait for anything, and return
+ * immediately. */
+ return determine_change(j);
+ }
+
+ if (j->on_network) {
+ /* If we are on the network we need to regularly check
+ * for changes manually */
+
+ if (timeout_usec == (uint64_t) -1 || timeout_usec > JOURNAL_FILES_RECHECK_USEC)
+ timeout_usec = JOURNAL_FILES_RECHECK_USEC;
+ }
+
+ do {
+ r = fd_wait_for_event(j->inotify_fd, POLLIN, timeout_usec);
+ } while (r == -EINTR);
+
+ if (r < 0)
+ return r;
+
+ return sd_journal_process(j);
+}
+
+_public_ int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to) {
+ Iterator i;
+ JournalFile *f;
+ bool first = true;
+ int r;
+
+ if (!j)
+ return -EINVAL;
+ if (!from && !to)
+ return -EINVAL;
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ usec_t fr, t;
+
+ r = journal_file_get_cutoff_realtime_usec(f, &fr, &t);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (first) {
+ if (from)
+ *from = fr;
+ if (to)
+ *to = t;
+ first = false;
+ } else {
+ if (from)
+ *from = MIN(fr, *from);
+ if (to)
+ *to = MAX(t, *to);
+ }
+ }
+
+ return first ? 0 : 1;
+}
+
+_public_ int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t *from, uint64_t *to) {
+ Iterator i;
+ JournalFile *f;
+ bool first = true;
+ int r;
+
+ if (!j)
+ return -EINVAL;
+ if (!from && !to)
+ return -EINVAL;
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ usec_t fr, t;
+
+ r = journal_file_get_cutoff_monotonic_usec(f, boot_id, &fr, &t);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (first) {
+ if (from)
+ *from = fr;
+ if (to)
+ *to = t;
+ first = false;
+ } else {
+ if (from)
+ *from = MIN(fr, *from);
+ if (to)
+ *to = MAX(t, *to);
+ }
+ }
+
+ return first ? 0 : 1;
+}
+
+void journal_print_header(sd_journal *j) {
+ Iterator i;
+ JournalFile *f;
+ bool newline = false;
+
+ assert(j);
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ if (newline)
+ putchar('\n');
+ else
+ newline = true;
+
+ journal_file_print_header(f);
+ }
+}
+
+_public_ int sd_journal_get_usage(sd_journal *j, uint64_t *bytes) {
+ Iterator i;
+ JournalFile *f;
+ uint64_t sum = 0;
+
+ if (!j)
+ return -EINVAL;
+ if (!bytes)
+ return -EINVAL;
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ struct stat st;
+
+ if (fstat(f->fd, &st) < 0)
+ return -errno;
+
+ sum += (uint64_t) st.st_blocks * 512ULL;
+ }
+
+ *bytes = sum;
+ return 0;
+}
+
+_public_ int sd_journal_query_unique(sd_journal *j, const char *field) {
+ char *f;
+
+ if (!j)
+ return -EINVAL;
+ if (isempty(field))
+ return -EINVAL;
+ if (!field_is_valid(field))
+ return -EINVAL;
+
+ f = strdup(field);
+ if (!f)
+ return -ENOMEM;
+
+ free(j->unique_field);
+ j->unique_field = f;
+ j->unique_file = NULL;
+ j->unique_offset = 0;
+
+ return 0;
+}
+
+_public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l) {
+ Object *o;
+ size_t k;
+ int r;
+
+ if (!j)
+ return -EINVAL;
+ if (!data)
+ return -EINVAL;
+ if (!l)
+ return -EINVAL;
+ if (!j->unique_field)
+ return -EINVAL;
+
+ k = strlen(j->unique_field);
+
+ if (!j->unique_file) {
+ j->unique_file = hashmap_first(j->files);
+ if (!j->unique_file)
+ return 0;
+ j->unique_offset = 0;
+ }
+
+ for (;;) {
+ JournalFile *of;
+ Iterator i;
+ const void *odata;
+ size_t ol;
+ bool found;
+
+ /* Proceed to next data object in the field's linked list */
+ if (j->unique_offset == 0) {
+ r = journal_file_find_field_object(j->unique_file, j->unique_field, k, &o, NULL);
+ if (r < 0)
+ return r;
+
+ j->unique_offset = r > 0 ? le64toh(o->field.head_data_offset) : 0;
+ } else {
+ r = journal_file_move_to_object(j->unique_file, OBJECT_DATA, j->unique_offset, &o);
+ if (r < 0)
+ return r;
+
+ j->unique_offset = le64toh(o->data.next_field_offset);
+ }
+
+ /* We reached the end of the list? Then start again, with the next file */
+ if (j->unique_offset == 0) {
+ JournalFile *n;
+
+ n = hashmap_next(j->files, j->unique_file->path);
+ if (!n)
+ return 0;
+
+ j->unique_file = n;
+ continue;
+ }
+
+ /* We do not use the type context here, but 0 instead,
+ * so that we can look at this data object at the same
+ * time as one on another file */
+ r = journal_file_move_to_object(j->unique_file, 0, j->unique_offset, &o);
+ if (r < 0)
+ return r;
+
+ /* Let's do the type check by hand, since we used 0 context above. */
+ if (o->object.type != OBJECT_DATA)
+ return -EBADMSG;
+
+ r = return_data(j->unique_file, o, &odata, &ol);
+ if (r < 0)
+ return r;
+
+ /* OK, now let's see if we already returned this data
+ * object by checking if it exists in the earlier
+ * traversed files. */
+ found = false;
+ HASHMAP_FOREACH(of, j->files, i) {
+ Object *oo;
+ uint64_t op;
+
+ if (of == j->unique_file)
+ break;
+
+ /* Skip this file it didn't have any fields
+ * indexed */
+ if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) &&
+ le64toh(of->header->n_fields) <= 0)
+ continue;
+
+ r = journal_file_find_data_object_with_hash(of, odata, ol, le64toh(o->data.hash), &oo, &op);
+ if (r < 0)
+ return r;
+
+ if (r > 0)
+ found = true;
+ }
+
+ if (found)
+ continue;
+
+ r = return_data(j->unique_file, o, data, l);
+ if (r < 0)
+ return r;
+
+ return 1;
+ }
+}
+
+_public_ void sd_journal_restart_unique(sd_journal *j) {
+ if (!j)
+ return;
+
+ j->unique_file = NULL;
+ j->unique_offset = 0;
+}
+
+_public_ int sd_journal_reliable_fd(sd_journal *j) {
+ if (!j)
+ return -EINVAL;
+
+ return !j->on_network;
+}
diff --git a/src/journal/test-journal-enum.c b/src/journal/test-journal-enum.c
new file mode 100644
index 0000000000..8a843ecdda
--- /dev/null
+++ b/src/journal/test-journal-enum.c
@@ -0,0 +1,53 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+
+#include "log.h"
+#include "sd-journal.h"
+
+int main(int argc, char *argv[]) {
+ unsigned n = 0;
+ sd_journal *j;
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY) >= 0);
+
+ assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "_UID=0", 0) >= 0);
+
+ SD_JOURNAL_FOREACH_BACKWARDS(j) {
+ const void *d;
+ size_t l;
+
+ assert_se(sd_journal_get_data(j, "MESSAGE", &d, &l) >= 0);
+
+ printf("%.*s\n", (int) l, (char*) d);
+
+ n ++;
+ if (n >= 10)
+ break;
+ }
+
+ sd_journal_close(j);
+ return 0;
+}
diff --git a/src/journal/test-journal-match.c b/src/journal/test-journal-match.c
new file mode 100644
index 0000000000..fa228144f5
--- /dev/null
+++ b/src/journal/test-journal-match.c
@@ -0,0 +1,67 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+
+#include <systemd/sd-journal.h>
+
+#include "journal-internal.h"
+#include "util.h"
+#include "log.h"
+
+int main(int argc, char *argv[]) {
+ sd_journal *j;
+ char *t;
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(sd_journal_open(&j, 0) >= 0);
+
+ assert_se(sd_journal_add_match(j, "foobar", 0) < 0);
+ assert_se(sd_journal_add_match(j, "foobar=waldo", 0) < 0);
+ assert_se(sd_journal_add_match(j, "", 0) < 0);
+ assert_se(sd_journal_add_match(j, "=", 0) < 0);
+ assert_se(sd_journal_add_match(j, "=xxxxx", 0) < 0);
+ assert_se(sd_journal_add_match(j, "HALLO=WALDO", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "QUUX=mmmm", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "HALLO=", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "QUUX=yyyyy", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "PIFF=paff", 0) >= 0);
+
+ assert_se(sd_journal_add_disjunction(j) >= 0);
+
+ assert_se(sd_journal_add_match(j, "ONE=one", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "ONE=two", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "TWO=two", 0) >= 0);
+
+ assert_se(t = journal_make_match_string(j));
+
+ assert_se(streq(t, "((TWO=two AND (ONE=two OR ONE=one)) OR (PIFF=paff AND (QUUX=yyyyy OR QUUX=xxxxx OR QUUX=mmmm) AND (HALLO= OR HALLO=WALDO)))"));
+
+ printf("resulting match expression is: %s\n", t);
+ free(t);
+
+ sd_journal_close(j);
+
+ return 0;
+}
diff --git a/src/journal/test-journal-send.c b/src/journal/test-journal-send.c
new file mode 100644
index 0000000000..3e986ed99a
--- /dev/null
+++ b/src/journal/test-journal-send.c
@@ -0,0 +1,78 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <systemd/sd-journal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "log.h"
+
+int main(int argc, char *argv[]) {
+ char huge[4096*1024];
+
+ log_set_max_level(LOG_DEBUG);
+
+ sd_journal_print(LOG_INFO, "piepapo");
+
+ sd_journal_send("MESSAGE=foobar",
+ "VALUE=%i", 7,
+ NULL);
+
+ errno = ENOENT;
+ sd_journal_perror("Foobar");
+
+ sd_journal_perror("");
+
+ memset(huge, 'x', sizeof(huge));
+ memcpy(huge, "HUGE=", 5);
+ char_array_0(huge);
+
+ sd_journal_send("MESSAGE=Huge field attached",
+ huge,
+ NULL);
+
+ sd_journal_send("MESSAGE=uiui",
+ "VALUE=A",
+ "VALUE=B",
+ "VALUE=C",
+ "SINGLETON=1",
+ "OTHERVALUE=X",
+ "OTHERVALUE=Y",
+ "WITH_BINARY=this is a binary value \a",
+ NULL);
+
+ syslog(LOG_NOTICE, "Hello World!");
+
+ sd_journal_print(LOG_NOTICE, "Hello World");
+
+ sd_journal_send("MESSAGE=Hello World!",
+ "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555",
+ "PRIORITY=5",
+ "HOME=%s", getenv("HOME"),
+ "TERM=%s", getenv("TERM"),
+ "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE),
+ "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN),
+ NULL);
+
+ sleep(10);
+
+ return 0;
+}
diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c
new file mode 100644
index 0000000000..b3e816db70
--- /dev/null
+++ b/src/journal/test-journal-stream.c
@@ -0,0 +1,185 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <systemd/sd-journal.h>
+
+#include "journal-file.h"
+#include "journal-internal.h"
+#include "util.h"
+#include "log.h"
+
+#define N_ENTRIES 200
+
+static void verify_contents(sd_journal *j, unsigned skip) {
+ unsigned i;
+
+ assert(j);
+
+ i = 0;
+ SD_JOURNAL_FOREACH(j) {
+ const void *d;
+ char *k, *c;
+ size_t l;
+ unsigned u;
+
+ assert_se(sd_journal_get_cursor(j, &k) >= 0);
+ printf("cursor: %s\n", k);
+ free(k);
+
+ assert_se(sd_journal_get_data(j, "MAGIC", &d, &l) >= 0);
+ printf("\t%.*s\n", (int) l, (const char*) d);
+
+ assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0);
+ assert_se(k = strndup(d, l));
+ printf("\t%s\n", k);
+
+ if (skip > 0) {
+ assert_se(safe_atou(k + 7, &u) >= 0);
+ assert_se(i == u);
+ i += 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)
+ assert_se(i == N_ENTRIES);
+}
+
+int main(int argc, char *argv[]) {
+ JournalFile *one, *two, *three;
+ char t[] = "/tmp/journal-stream-XXXXXX";
+ unsigned i;
+ sd_journal *j;
+ char *z;
+ const void *data;
+ size_t l;
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+
+ assert_se(journal_file_open("one.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, &one) == 0);
+ assert_se(journal_file_open("two.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, &two) == 0);
+ assert_se(journal_file_open("three.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, &three) == 0);
+
+ for (i = 0; i < N_ENTRIES; i++) {
+ char *p, *q;
+ dual_timestamp ts;
+ struct iovec iovec[2];
+
+ dual_timestamp_get(&ts);
+
+ assert_se(asprintf(&p, "NUMBER=%u", i) >= 0);
+ iovec[0].iov_base = p;
+ iovec[0].iov_len = strlen(p);
+
+ assert_se(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo") >= 0);
+
+ iovec[1].iov_base = q;
+ iovec[1].iov_len = strlen(q);
+
+ if (i % 10 == 0)
+ assert_se(journal_file_append_entry(three, &ts, iovec, 2, NULL, NULL, NULL) == 0);
+ else {
+ if (i % 3 == 0)
+ assert_se(journal_file_append_entry(two, &ts, iovec, 2, NULL, NULL, NULL) == 0);
+
+ assert_se(journal_file_append_entry(one, &ts, iovec, 2, NULL, NULL, NULL) == 0);
+ }
+
+ free(p);
+ free(q);
+ }
+
+ journal_file_close(one);
+ journal_file_close(two);
+ journal_file_close(three);
+
+ assert_se(sd_journal_open_directory(&j, t, 0) >= 0);
+
+ assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0);
+ SD_JOURNAL_FOREACH_BACKWARDS(j) {
+ char *c;
+
+ assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0);
+ printf("\t%.*s\n", (int) l, (const char*) data);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ free(c);
+ }
+
+ SD_JOURNAL_FOREACH(j) {
+ char *c;
+
+ assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0);
+ printf("\t%.*s\n", (int) l, (const char*) data);
+
+ 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);
+
+ verify_contents(j, 1);
+
+ printf("NEXT TEST\n");
+ assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0);
+
+ assert_se(z = journal_make_match_string(j));
+ printf("resulting match expression is: %s\n", z);
+ free(z);
+
+ verify_contents(j, 5);
+
+ printf("NEXT TEST\n");
+ sd_journal_flush_matches(j);
+ assert_se(sd_journal_add_match(j, "MAGIC=waldo", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=10", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=11", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=12", 0) >= 0);
+
+ assert_se(z = journal_make_match_string(j));
+ printf("resulting match expression is: %s\n", z);
+ free(z);
+
+ verify_contents(j, 0);
+
+ assert_se(sd_journal_query_unique(j, "NUMBER") >= 0);
+ SD_JOURNAL_FOREACH_UNIQUE(j, data, l)
+ printf("%.*s\n", (int) l, (const char*) data);
+
+ sd_journal_close(j);
+
+ assert_se(rm_rf_dangerous(t, false, true, false) >= 0);
+
+ return 0;
+}
diff --git a/src/journal/test-journal-syslog.c b/src/journal/test-journal-syslog.c
new file mode 100644
index 0000000000..3ae8633f22
--- /dev/null
+++ b/src/journal/test-journal-syslog.c
@@ -0,0 +1,44 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journald-syslog.h"
+#include "macro.h"
+
+static void test_syslog_parse_identifier(const char* str,
+ const char *ident, const char*pid, int ret) {
+ const char *buf = str;
+ char *ident2 = NULL, *pid2 = NULL;
+ int ret2;
+
+ ret2 = syslog_parse_identifier(&buf, &ident2, &pid2);
+
+ assert(ret == ret2);
+ assert(ident==ident2 || !strcmp(ident, ident2));
+ assert(pid==pid2 || !strcmp(pid, pid2));
+}
+
+int main(void) {
+ test_syslog_parse_identifier("pidu[111]: xxx", "pidu", "111", 11);
+ test_syslog_parse_identifier("pidu: xxx", "pidu", NULL, 6);
+ test_syslog_parse_identifier("pidu xxx", NULL, NULL, 0);
+
+ return 0;
+}
diff --git a/src/journal/test-journal-verify.c b/src/journal/test-journal-verify.c
new file mode 100644
index 0000000000..b6677215c0
--- /dev/null
+++ b/src/journal/test-journal-verify.c
@@ -0,0 +1,147 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "util.h"
+#include "log.h"
+#include "journal-file.h"
+#include "journal-verify.h"
+#include "journal-authenticate.h"
+
+#define N_ENTRIES 6000
+#define RANDOM_RANGE 77
+
+static void bit_toggle(const char *fn, uint64_t p) {
+ uint8_t b;
+ ssize_t r;
+ int fd;
+
+ fd = open(fn, O_RDWR|O_CLOEXEC);
+ assert(fd >= 0);
+
+ r = pread(fd, &b, 1, p/8);
+ assert(r == 1);
+
+ b ^= 1 << (p % 8);
+
+ r = pwrite(fd, &b, 1, p/8);
+ assert(r == 1);
+
+ close_nointr_nofail(fd);
+}
+
+static int raw_verify(const char *fn, const char *verification_key) {
+ JournalFile *f;
+ int r;
+
+ r = journal_file_open(fn, O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, &f);
+ if (r < 0)
+ return r;
+
+ r = journal_file_verify(f, verification_key, NULL, NULL, NULL, false);
+ journal_file_close(f);
+
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ char t[] = "/tmp/journal-XXXXXX";
+ unsigned n;
+ JournalFile *f;
+ const char *verification_key = argv[1];
+ usec_t from = 0, to = 0, total = 0;
+ char a[FORMAT_TIMESTAMP_MAX];
+ char b[FORMAT_TIMESTAMP_MAX];
+ char c[FORMAT_TIMESPAN_MAX];
+ struct stat st;
+ uint64_t p;
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+
+ log_info("Generating...");
+
+ assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, true, !!verification_key, NULL, NULL, NULL, &f) == 0);
+
+ for (n = 0; n < N_ENTRIES; n++) {
+ struct iovec iovec;
+ struct dual_timestamp ts;
+ char *test;
+
+ dual_timestamp_get(&ts);
+
+ assert_se(asprintf(&test, "RANDOM=%lu", random() % RANDOM_RANGE));
+
+ iovec.iov_base = (void*) test;
+ iovec.iov_len = strlen(test);
+
+ assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+ free(test);
+ }
+
+ journal_file_close(f);
+
+ log_info("Verifying...");
+
+ assert_se(journal_file_open("test.journal", O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, &f) == 0);
+ /* journal_file_print_header(f); */
+ journal_file_dump(f);
+
+ assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0);
+
+ if (verification_key && JOURNAL_HEADER_SEALED(f->header)) {
+ log_info("=> Validated from %s to %s, %s missing",
+ format_timestamp(a, sizeof(a), from),
+ format_timestamp(b, sizeof(b), to),
+ format_timespan(c, sizeof(c), total > to ? total - to : 0));
+ }
+
+ journal_file_close(f);
+
+ if (verification_key) {
+ log_info("Toggling bits...");
+
+ assert_se(stat("test.journal", &st) >= 0);
+
+ for (p = 38448*8+0; p < ((uint64_t) st.st_size * 8); p ++) {
+ bit_toggle("test.journal", p);
+
+ log_info("[ %llu+%llu]", (unsigned long long) p / 8, (unsigned long long) p % 8);
+
+ if (raw_verify("test.journal", verification_key) >= 0)
+ log_notice(ANSI_HIGHLIGHT_RED_ON ">>>> %llu (bit %llu) can be toggled without detection." ANSI_HIGHLIGHT_OFF, (unsigned long long) p / 8, (unsigned long long) p % 8);
+
+ bit_toggle("test.journal", p);
+ }
+ }
+
+ log_info("Exiting...");
+
+ assert_se(rm_rf_dangerous(t, false, true, false) >= 0);
+
+ return 0;
+}
diff --git a/src/journal/test-journal.c b/src/journal/test-journal.c
new file mode 100644
index 0000000000..f4dc52cd81
--- /dev/null
+++ b/src/journal/test-journal.c
@@ -0,0 +1,129 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <systemd/sd-journal.h>
+
+#include "log.h"
+#include "journal-file.h"
+#include "journal-authenticate.h"
+#include "journal-vacuum.h"
+
+int main(int argc, char *argv[]) {
+ dual_timestamp ts;
+ JournalFile *f;
+ struct iovec iovec;
+ static const char test[] = "TEST1=1", test2[] = "TEST2=2";
+ Object *o;
+ uint64_t p;
+ char t[] = "/tmp/journal-XXXXXX";
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+
+ assert_se(journal_file_open("test.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, &f) == 0);
+
+ dual_timestamp_get(&ts);
+
+ iovec.iov_base = (void*) test;
+ iovec.iov_len = strlen(test);
+ assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+ iovec.iov_base = (void*) test2;
+ iovec.iov_len = strlen(test2);
+ assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+ iovec.iov_base = (void*) test;
+ iovec.iov_len = strlen(test);
+ assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+#ifdef HAVE_GCRYPT
+ journal_file_append_tag(f);
+#endif
+ journal_file_dump(f);
+
+ assert(journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p) == 1);
+ assert(le64toh(o->entry.seqnum) == 1);
+
+ assert(journal_file_next_entry(f, o, p, DIRECTION_DOWN, &o, &p) == 1);
+ assert(le64toh(o->entry.seqnum) == 2);
+
+ assert(journal_file_next_entry(f, o, p, DIRECTION_DOWN, &o, &p) == 1);
+ assert(le64toh(o->entry.seqnum) == 3);
+
+ assert(journal_file_next_entry(f, o, p, DIRECTION_DOWN, &o, &p) == 0);
+
+ assert(journal_file_next_entry(f, NULL, 0, DIRECTION_DOWN, &o, &p) == 1);
+ assert(le64toh(o->entry.seqnum) == 1);
+
+ assert(journal_file_skip_entry(f, o, p, 2, &o, &p) == 1);
+ assert(le64toh(o->entry.seqnum) == 3);
+
+ assert(journal_file_skip_entry(f, o, p, -2, &o, &p) == 1);
+ assert(le64toh(o->entry.seqnum) == 1);
+
+ assert(journal_file_skip_entry(f, o, p, -2, &o, &p) == 1);
+ assert(le64toh(o->entry.seqnum) == 1);
+
+ assert(journal_file_find_data_object(f, test, strlen(test), NULL, &p) == 1);
+ assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1);
+ assert(le64toh(o->entry.seqnum) == 1);
+
+ assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1);
+ assert(le64toh(o->entry.seqnum) == 3);
+
+ assert(journal_file_find_data_object(f, test2, strlen(test2), NULL, &p) == 1);
+ assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1);
+ assert(le64toh(o->entry.seqnum) == 2);
+
+ assert(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1);
+ assert(le64toh(o->entry.seqnum) == 2);
+
+ assert(journal_file_find_data_object(f, "quux", 4, NULL, &p) == 0);
+
+ assert(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1);
+ assert(le64toh(o->entry.seqnum) == 1);
+
+ assert(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1);
+ assert(le64toh(o->entry.seqnum) == 3);
+
+ assert(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1);
+ assert(le64toh(o->entry.seqnum) == 2);
+
+ assert(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0);
+
+ journal_file_rotate(&f, true, true);
+ journal_file_rotate(&f, true, true);
+
+ journal_file_close(f);
+
+ journal_directory_vacuum(".", 3000000, 0, 0, NULL);
+
+ log_error("Exiting...");
+
+ assert_se(rm_rf_dangerous(t, false, true, false) >= 0);
+
+ return 0;
+}
diff --git a/src/journal/test-mmap-cache.c b/src/journal/test-mmap-cache.c
new file mode 100644
index 0000000000..e2ffaf4723
--- /dev/null
+++ b/src/journal/test-mmap-cache.c
@@ -0,0 +1,79 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "macro.h"
+#include "util.h"
+#include "mmap-cache.h"
+
+int main(int argc, char *argv[]) {
+ int x, y, z, r;
+ char px[] = "/tmp/testmmapXXXXXXX", py[] = "/tmp/testmmapYXXXXXX", pz[] = "/tmp/testmmapZXXXXXX";
+ MMapCache *m;
+ void *p, *q;
+
+ assert_se(m = mmap_cache_new());
+
+ x = mkstemp(px);
+ assert(x >= 0);
+ unlink(px);
+
+ y = mkstemp(py);
+ assert(y >= 0);
+ unlink(py);
+
+ z = mkstemp(pz);
+ assert(z >= 0);
+ unlink(pz);
+
+ r = mmap_cache_get(m, x, PROT_READ, 0, false, 1, 2, NULL, &p);
+ assert(r >= 0);
+
+ r = mmap_cache_get(m, x, PROT_READ, 0, false, 2, 2, NULL, &q);
+ assert(r >= 0);
+
+ assert((uint8_t*) p + 1 == (uint8_t*) q);
+
+ r = mmap_cache_get(m, x, PROT_READ, 1, false, 3, 2, NULL, &q);
+ assert(r >= 0);
+
+ assert((uint8_t*) p + 2 == (uint8_t*) q);
+
+ r = mmap_cache_get(m, x, PROT_READ, 0, false, 16ULL*1024ULL*1024ULL, 2, NULL, &p);
+ assert(r >= 0);
+
+ r = mmap_cache_get(m, x, PROT_READ, 1, false, 16ULL*1024ULL*1024ULL+1, 2, NULL, &q);
+ assert(r >= 0);
+
+ assert((uint8_t*) p + 1 == (uint8_t*) q);
+
+ mmap_cache_unref(m);
+
+ close_nointr_nofail(x);
+ close_nointr_nofail(y);
+ close_nointr_nofail(z);
+
+ return 0;
+}