summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am29
-rw-r--r--catalog/systemd.catalog99
-rw-r--r--man/journalctl.xml39
-rw-r--r--src/journal/catalog.c584
-rw-r--r--src/journal/catalog.h28
-rw-r--r--src/journal/journalctl.c41
-rw-r--r--src/journal/libsystemd-journal.sym1
-rw-r--r--src/journal/sd-journal.c60
-rw-r--r--src/journal/test-catalog.c48
-rw-r--r--src/shared/logs-show.c26
-rw-r--r--src/shared/logs-show.h3
-rw-r--r--src/systemd/sd-journal.h2
13 files changed, 952 insertions, 9 deletions
diff --git a/.gitignore b/.gitignore
index 3fbd83e0e6..2291e5d20f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+/test-catalog
/test-replace-var
/test-journal-enum
/test-sleep
diff --git a/Makefile.am b/Makefile.am
index 42fed59641..3c590094d3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -78,9 +78,10 @@ systemsleepdir=$(rootlibexecdir)/system-sleep
systemunitdir=$(rootprefix)/lib/systemd/system
systempresetdir=$(rootprefix)/lib/systemd/system-preset
udevlibexecdir=$(rootprefix)/lib/udev
-udevhomedir = $(udevlibexecdir)
-udevrulesdir = $(udevlibexecdir)/rules.d
-udevhwdbdir = $(udevlibexecdir)/hwdb.d
+udevhomedir=$(udevlibexecdir)
+udevrulesdir=$(udevlibexecdir)/rules.d
+udevhwdbdir=$(udevlibexecdir)/hwdb.d
+catalogdir=$(prefix)/lib/systemd/catalog
# And these are the special ones for /
rootprefix=@rootprefix@
@@ -2565,6 +2566,15 @@ test_mmap_cache_LDADD = \
libsystemd-shared.la \
libsystemd-journal-internal.la
+test_catalog_SOURCES = \
+ src/journal/test-catalog.c
+
+test_catalog_LDADD = \
+ libsystemd-shared.la \
+ libsystemd-label.la \
+ libsystemd-journal-internal.la \
+ libsystemd-id128-internal.la
+
libsystemd_journal_la_SOURCES = \
src/journal/sd-journal.c \
src/systemd/sd-journal.h \
@@ -2579,6 +2589,8 @@ libsystemd_journal_la_SOURCES = \
src/journal/journal-send.c \
src/journal/journal-def.h \
src/journal/compress.h \
+ src/journal/catalog.c \
+ src/journal/catalog.h \
src/journal/mmap-cache.c \
src/journal/mmap-cache.h
@@ -2594,6 +2606,7 @@ libsystemd_journal_la_LDFLAGS = \
libsystemd_journal_la_LIBADD = \
libsystemd-shared.la \
+ libsystemd-label.la \
libsystemd-id128-internal.la
libsystemd_journal_internal_la_SOURCES = \
@@ -2621,7 +2634,9 @@ libsystemd_journal_internal_la_LIBADD = \
libsystemd-label.la \
libsystemd-audit.la \
libsystemd-daemon.la \
- libudev.la
+ libudev.la \
+ libsystemd-shared.la \
+ libsystemd-label.la
nodist_libsystemd_journal_internal_la_SOURCES = \
src/journal/journald-gperf.c
@@ -2703,7 +2718,8 @@ noinst_PROGRAMS += \
test-journal-enum \
test-journal-stream \
test-journal-verify \
- test-mmap-cache
+ test-mmap-cache \
+ test-catalog
TESTS += \
test-journal \
@@ -2747,6 +2763,9 @@ dist_pkgsysconf_DATA += \
pkgconfiglib_DATA += \
src/journal/libsystemd-journal.pc
+dist_catalog_DATA = \
+ catalog/systemd.catalog
+
journal-install-data-hook:
$(MKDIR_P) -m 0755 \
$(DESTDIR)$(systemunitdir)/sockets.target.wants \
diff --git a/catalog/systemd.catalog b/catalog/systemd.catalog
new file mode 100644
index 0000000000..91d040800a
--- /dev/null
+++ b/catalog/systemd.catalog
@@ -0,0 +1,99 @@
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Process @COREDUMP_PID@ (@COREDUMP_COMM@) dumped core
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+
+Process @COREDUMP_PID@ (@COREDUMP_COMM@) crashed and dumped core.
+
+This usually indicates a programming error in the crashing program and
+should be reported to the vendor as a bug.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1 de
+Subject: Speicherabbild für Prozess @COREDUMP_PID@ (@COREDUMP_COMM) generiert
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+
+Prozess @COREDUMP_PID@ (@COREDUMP_COMM@) ist abgebrochen worden und
+ein Speicherabbild wurde generiert.
+
+Üblicherweise ist dies ein Hinweis auf einen Programmfehler und sollte
+als Fehler dem Hersteller gemeldet werden.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Time change
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+
+The system clock has been changed.
+
+-- c7a787079b354eaaa9e77b371893cd27 de
+Subject: Zeitänderung
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+
+Die System-Zeit wurde geändert.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Time zone change
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+
+The system time zone has been changed.
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: The Journal has been started
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+
+The system journal process has been starting up, opened the journal
+files for writing and is now ready to process requests.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: The Journal has been stopped
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+
+The system journal process has shut down and closed all currently
+active journal files.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: A new seat @SEAT_ID@ is now available
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+
+A new seat @SEAT_ID@ has been configured and is now available.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: A new session @SESSION_ID@ has been created for user @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+
+A new session with the ID @SESSION_ID@ has been created for the user @USER_ID@.
+
+The leading process of the session is @LEADER@.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Messages from a service have been suppressed
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Developer-Description: http://www.freedesktop.org/wiki/Software/systemd/catalog/@MESSAGE_ID@
+See: man:journald.conf(5)
+
+A service has logged too many messages within a time period. Messages
+from the service have been dropped.
+
+Note that only messages from the service in question have been
+dropped, other services' messages are unaffected.
+
+The limits when messages are dropped may be configured with
+RateLimitInterval= and RateLimitBurst= in
+/etc/systemd/journald.conf. See journald.conf(5) for details.
diff --git a/man/journalctl.xml b/man/journalctl.xml
index 026bb22940..e7fff0c9dc 100644
--- a/man/journalctl.xml
+++ b/man/journalctl.xml
@@ -233,6 +233,25 @@
</varlistentry>
<varlistentry>
+ <term><option>--catalog</option></term>
+ <term><option>-x</option></term>
+
+ <listitem><para>Augment log lines with
+ explanation texts from the message
+ catalog. This will add explanatory
+ help texts to log messages in the
+ output where this is available. These
+ short help texts will explain the
+ context of an error or log event,
+ possible solutions, as well as
+ pointers to support forums, developer
+ documentation and any other relevant
+ manuals. Note that help texts are not
+ available for all messages but only
+ for selected ones.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>--quiet</option></term>
<term><option>-q</option></term>
@@ -405,6 +424,26 @@
</varlistentry>
<varlistentry>
+ <term><option>--list-catalog</option></term>
+
+ <listitem><para>List the contents of
+ the message catalog, as table of
+ message IDs plus their short
+ description strings.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--update-catalog</option></term>
+
+ <listitem><para>Update the message
+ catalog index. This command needs to
+ be executed each time new catalog
+ files are installed, removed or
+ updated to rebuild the binary catalog
+ index.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>--setup-keys</option></term>
<listitem><para>Instead of showing
diff --git a/src/journal/catalog.c b/src/journal/catalog.c
new file mode 100644
index 0000000000..7be0d20f42
--- /dev/null
+++ b/src/journal/catalog.c
@@ -0,0 +1,584 @@
+/*-*- 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 <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <locale.h>
+
+#include "util.h"
+#include "log.h"
+#include "sparse-endian.h"
+#include "sd-id128.h"
+#include "hashmap.h"
+#include "strv.h"
+#include "strbuf.h"
+#include "conf-files.h"
+#include "mkdir.h"
+#include "catalog.h"
+
+static const char * const conf_file_dirs[] = {
+ "/usr/local/lib/systemd/catalog/",
+ "/usr/lib/systemd/catalog/",
+ NULL
+};
+
+#define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
+
+typedef struct CatalogHeader {
+ uint8_t signature[8]; /* "RHHHKSLP" */
+ le32_t compatible_flags;
+ le32_t incompatible_flags;
+ le32_t header_size;
+ le32_t n_items;
+} CatalogHeader;
+
+typedef struct CatalogItem {
+ sd_id128_t id;
+ char language[32];
+ le32_t offset;
+} CatalogItem;
+
+static unsigned catalog_hash_func(const void *p) {
+ const CatalogItem *i = p;
+
+ assert_cc(sizeof(unsigned) == sizeof(uint8_t)*4);
+
+ return (((unsigned) i->id.bytes[0] << 24) |
+ ((unsigned) i->id.bytes[1] << 16) |
+ ((unsigned) i->id.bytes[2] << 8) |
+ ((unsigned) i->id.bytes[3])) ^
+ (((unsigned) i->id.bytes[4] << 24) |
+ ((unsigned) i->id.bytes[5] << 16) |
+ ((unsigned) i->id.bytes[6] << 8) |
+ ((unsigned) i->id.bytes[7])) ^
+ (((unsigned) i->id.bytes[8] << 24) |
+ ((unsigned) i->id.bytes[9] << 16) |
+ ((unsigned) i->id.bytes[10] << 8) |
+ ((unsigned) i->id.bytes[11])) ^
+ (((unsigned) i->id.bytes[12] << 24) |
+ ((unsigned) i->id.bytes[13] << 16) |
+ ((unsigned) i->id.bytes[14] << 8) |
+ ((unsigned) i->id.bytes[15])) ^
+ string_hash_func(i->language);
+}
+
+static int catalog_compare_func(const void *a, const void *b) {
+ const CatalogItem *i = a, *j = b;
+ unsigned k;
+
+ for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) {
+ if (i->id.bytes[k] < j->id.bytes[k])
+ return -1;
+ if (i->id.bytes[k] > j->id.bytes[k])
+ return 1;
+ }
+
+ return strncmp(i->language, j->language, sizeof(i->language));
+}
+
+static int finish_item(
+ Hashmap *h,
+ struct strbuf *sb,
+ sd_id128_t id,
+ const char *language,
+ const char *payload) {
+
+ ssize_t offset;
+ CatalogItem *i;
+ int r;
+
+ assert(h);
+ assert(sb);
+ assert(payload);
+
+ offset = strbuf_add_string(sb, payload, strlen(payload));
+ if (offset < 0)
+ return log_oom();
+
+ if (offset > 0xFFFFFFFF) {
+ log_error("Too many catalog entries.");
+ return -E2BIG;
+ }
+
+ i = new0(CatalogItem, 1);
+ if (!i)
+ return log_oom();
+
+ i->id = id;
+ strncpy(i->language, language, sizeof(i->language));
+ i->offset = htole32((uint32_t) offset);
+
+ r = hashmap_put(h, i, i);
+ if (r == EEXIST) {
+ log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.", SD_ID128_FORMAT_VAL(id), language ? language : "C");
+ free(i);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int import_file(Hashmap *h, struct strbuf *sb, const char *path) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *payload = NULL;
+ unsigned n = 0;
+ sd_id128_t id;
+ char language[32];
+ bool got_id = false, empty_line = true;
+ int r;
+
+ assert(h);
+ assert(sb);
+ assert(path);
+
+ f = fopen(path, "re");
+ if (!f) {
+ log_error("Failed to open file %s: %m", path);
+ return -errno;
+ }
+
+ for (;;) {
+ char line[LINE_MAX];
+ size_t a, b, c;
+ char *t;
+
+ if (!fgets(line, sizeof(line), f)) {
+ if (feof(f))
+ break;
+
+ log_error("Failed to read file %s: %m", path);
+ return -errno;
+ }
+
+ n++;
+
+ truncate_nl(line);
+
+ if (line[0] == 0) {
+ empty_line = true;
+ continue;
+ }
+
+ if (strchr(COMMENTS, line[0]))
+ continue;
+
+ if (empty_line &&
+ strlen(line) >= 2+1+32 &&
+ line[0] == '-' &&
+ line[1] == '-' &&
+ line[2] == ' ' &&
+ (line[2+1+32] == ' ' || line[2+1+32] == 0)) {
+
+ bool with_language;
+ sd_id128_t jd;
+
+ /* New entry */
+
+ with_language = line[2+1+32] != 0;
+ line[2+1+32] = 0;
+
+ if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
+
+ if (got_id) {
+ r = finish_item(h, sb, id, language, payload);
+ if (r < 0)
+ return r;
+ }
+
+ if (with_language) {
+ t = strstrip(line + 2 + 1 + 32 + 1);
+
+ c = strlen(t);
+ if (c <= 0) {
+ log_error("[%s:%u] Language too short.", path, n);
+ return -EINVAL;
+ }
+ if (c > sizeof(language)) {
+ log_error("[%s:%u] language too long.", path, n);
+ return -EINVAL;
+ }
+
+ strncpy(language, t, sizeof(language));
+ } else
+ zero(language);
+
+ got_id = true;
+ empty_line = false;
+ id = jd;
+
+ if (payload)
+ payload[0] = 0;
+
+ continue;
+ }
+ }
+
+ /* Payload */
+ if (!got_id) {
+ log_error("[%s:%u] Got payload before ID.", path, n);
+ return -EINVAL;
+ }
+
+ a = payload ? strlen(payload) : 0;
+ b = strlen(line);
+
+ c = a + (empty_line ? 1 : 0) + b + 1 + 1;
+ t = realloc(payload, c);
+ if (!t)
+ return log_oom();
+
+ if (empty_line) {
+ t[a] = '\n';
+ memcpy(t + a + 1, line, b);
+ t[a+b+1] = '\n';
+ t[a+b+2] = 0;
+ } else {
+ memcpy(t + a, line, b);
+ t[a+b] = '\n';
+ t[a+b+1] = 0;
+ }
+
+ payload = t;
+ empty_line = false;
+ }
+
+ if (got_id) {
+ r = finish_item(h, sb, id, language, payload);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int catalog_update(void) {
+ _cleanup_strv_free_ char **files = NULL;
+ _cleanup_fclose_ FILE *w = NULL;
+ _cleanup_free_ char *p = NULL;
+ char **f;
+ Hashmap *h;
+ struct strbuf *sb = NULL;
+ _cleanup_free_ CatalogItem *items = NULL;
+ CatalogItem *i;
+ CatalogHeader header;
+ size_t k;
+ Iterator j;
+ unsigned n;
+ int r;
+
+ h = hashmap_new(catalog_hash_func, catalog_compare_func);
+ if (!h)
+ return -ENOMEM;
+
+ sb = strbuf_new();
+ if (!sb) {
+ r = log_oom();
+ goto finish;
+ }
+
+ r = conf_files_list_strv(&files, ".catalog", (const char **) conf_file_dirs);
+ if (r < 0) {
+ log_error("Failed to get catalog files: %s", strerror(-r));
+ goto finish;
+ }
+
+ STRV_FOREACH(f, files) {
+ log_debug("reading file '%s'", *f);
+ import_file(h, sb, *f);
+ }
+
+ if (hashmap_size(h) <= 0) {
+ log_info("No items in catalog.");
+ r = 0;
+ goto finish;
+ }
+
+ strbuf_complete(sb);
+
+ items = new(CatalogItem, hashmap_size(h));
+ if (!items) {
+ r = log_oom();
+ goto finish;
+ }
+
+ n = 0;
+ HASHMAP_FOREACH(i, h, j) {
+ log_debug("Found " SD_ID128_FORMAT_STR ", language %s", SD_ID128_FORMAT_VAL(i->id), isempty(i->language) ? "C" : i->language);
+ items[n++] = *i;
+ }
+
+ assert(n == hashmap_size(h));
+ qsort(items, n, sizeof(CatalogItem), catalog_compare_func);
+
+ mkdir_p("/var/lib/systemd/catalog", 0775);
+
+ r = fopen_temporary("/var/lib/systemd/catalog/database", &w, &p);
+ if (r < 0) {
+ log_error("Failed to open database for writing: %s", strerror(-r));
+ goto finish;
+ }
+
+ zero(header);
+ memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
+ header.header_size = htole32(ALIGN_TO(sizeof(CatalogHeader), 8));
+ header.n_items = htole32(hashmap_size(h));
+
+ k = fwrite(&header, 1, sizeof(header), w);
+ if (k != sizeof(header)) {
+ log_error("Failed to write header.");
+ goto finish;
+ }
+
+ k = fwrite(items, 1, n * sizeof(CatalogItem), w);
+ if (k != n * sizeof(CatalogItem)) {
+ log_error("Failed to write database.");
+ goto finish;
+ }
+
+ k = fwrite(sb->buf, 1, sb->len, w);
+ if (k != sb->len) {
+ log_error("Failed to write strings.");
+ goto finish;
+ }
+
+ fflush(w);
+
+ if (ferror(w)) {
+ log_error("Failed to write database.");
+ goto finish;
+ }
+
+ fchmod(fileno(w), 0644);
+
+ if (rename(p, "/var/lib/systemd/catalog/database") < 0) {
+ log_error("rename() failed: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ free(p);
+ p = NULL;
+
+ r = 0;
+
+finish:
+ hashmap_free_free(h);
+
+ if (sb)
+ strbuf_cleanup(sb);
+
+ if (p)
+ unlink(p);
+
+ return r;
+}
+
+static int open_mmap(int *_fd, struct stat *_st, void **_p) {
+ const CatalogHeader *h;
+ int fd;
+ void *p;
+ struct stat st;
+
+ assert(_fd);
+ assert(_st);
+ assert(_p);
+
+ fd = open("/var/lib/systemd/catalog/database", O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ if (fstat(fd, &st) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ if (st.st_size < (off_t) sizeof(CatalogHeader)) {
+ close_nointr_nofail(fd);
+ return -EINVAL;
+ }
+
+ p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
+ if (p == MAP_FAILED) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ h = p;
+ if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
+ le32toh(h->header_size) < sizeof(CatalogHeader) ||
+ h->incompatible_flags != 0 ||
+ le32toh(h->n_items) <= 0 ||
+ st.st_size < (off_t) (le32toh(h->header_size) + sizeof(CatalogItem) * le32toh(h->n_items))) {
+ close_nointr_nofail(fd);
+ munmap(p, st.st_size);
+ return -EBADMSG;
+ }
+
+ *_fd = fd;
+ *_st = st;
+ *_p = p;
+
+ return 0;
+}
+
+static const char *find_id(void *p, sd_id128_t id) {
+ CatalogItem key, *f = NULL;
+ const CatalogHeader *h = p;
+ const char *loc;
+
+ zero(key);
+ key.id = id;
+
+ loc = setlocale(LC_MESSAGES, NULL);
+ if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
+ strncpy(key.language, loc, sizeof(key.language));
+ key.language[strcspn(key.language, ".@")] = 0;
+
+ f = bsearch(&key, (const uint8_t*) p + le32toh(h->header_size), le32toh(h->n_items), sizeof(CatalogItem), catalog_compare_func);
+ if (!f) {
+ char *e;
+
+ e = strchr(key.language, '_');
+ if (e) {
+ *e = 0;
+ f = bsearch(&key, (const uint8_t*) p + le32toh(h->header_size), le32toh(h->n_items), sizeof(CatalogItem), catalog_compare_func);
+ }
+ }
+ }
+
+ if (!f) {
+ zero(key.language);
+ f = bsearch(&key, (const uint8_t*) p + le32toh(h->header_size), le32toh(h->n_items), sizeof(CatalogItem), catalog_compare_func);
+ }
+
+ if (!f)
+ return NULL;
+
+ return (const char*) p +
+ le32toh(h->header_size) +
+ le32toh(h->n_items) * sizeof(CatalogItem) +
+ le32toh(f->offset);
+}
+
+int catalog_get(sd_id128_t id, char **_text) {
+ _cleanup_close_ int fd = -1;
+ void *p = NULL;
+ struct stat st;
+ char *text = NULL;
+ int r;
+ const char *s;
+
+ assert(_text);
+
+ r = open_mmap(&fd, &st, &p);
+ if (r < 0)
+ return r;
+
+ s = find_id(p, id);
+ if (!s) {
+ r = -ENOENT;
+ goto finish;
+ }
+
+ text = strdup(s);
+ if (!text) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ *_text = text;
+ r = 0;
+
+finish:
+ if (p)
+ munmap(p, st.st_size);
+
+ return r;
+}
+
+static char *find_header(const char *s, const char *header) {
+
+ for (;;) {
+ const char *v, *e;
+
+ v = startswith(s, header);
+ if (v) {
+ v += strspn(v, WHITESPACE);
+ return strndup(v, strcspn(v, NEWLINE));
+ }
+
+ /* End of text */
+ e = strchr(s, '\n');
+ if (!e)
+ return NULL;
+
+ /* End of header */
+ if (e == s)
+ return NULL;
+
+ s = e + 1;
+ }
+}
+
+int catalog_list(FILE *f) {
+ _cleanup_close_ int fd = -1;
+ void *p = NULL;
+ struct stat st;
+ const CatalogHeader *h;
+ const CatalogItem *items;
+ int r;
+ unsigned n;
+ sd_id128_t last_id;
+ bool last_id_set = false;
+
+ r = open_mmap(&fd, &st, &p);
+ if (r < 0)
+ return r;
+
+ h = p;
+ items = (const CatalogItem*) ((const uint8_t*) p + le32toh(h->header_size));
+
+ for (n = 0; n < le32toh(h->n_items); n++) {
+ const char *s;
+ _cleanup_free_ char *subject = NULL, *defined_by = NULL;
+
+ if (last_id_set && sd_id128_equal(last_id, items[n].id))
+ continue;
+
+ assert_se(s = find_id(p, items[n].id));
+
+ subject = find_header(s, "Subject:");
+ defined_by = find_header(s, "Defined-By:");
+
+ fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n", SD_ID128_FORMAT_VAL(items[n].id), strna(defined_by), strna(subject));
+
+ last_id_set = true;
+ last_id = items[n].id;
+ }
+
+ munmap(p, st.st_size);
+
+ return 0;
+}
diff --git a/src/journal/catalog.h b/src/journal/catalog.h
new file mode 100644
index 0000000000..9add773c95
--- /dev/null
+++ b/src/journal/catalog.h
@@ -0,0 +1,28 @@
+/*-*- 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 "sd-id128.h"
+
+int catalog_update(void);
+int catalog_get(sd_id128_t id, char **data);
+int catalog_list(FILE *f);
diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
index 011a11b70b..46a7be20ce 100644
--- a/src/journal/journalctl.c
+++ b/src/journal/journalctl.c
@@ -51,6 +51,7 @@
#include "journal-qrcode.h"
#include "fsprg.h"
#include "unit-name.h"
+#include "catalog.h"
#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
@@ -74,6 +75,7 @@ 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 bool arg_catalog = false;
static enum {
ACTION_SHOW,
@@ -82,6 +84,8 @@ static enum {
ACTION_SETUP_KEYS,
ACTION_VERIFY,
ACTION_DISK_USAGE,
+ ACTION_LIST_CATALOG,
+ ACTION_UPDATE_CATALOG
} arg_action = ACTION_SHOW;
static int help(void) {
@@ -100,6 +104,7 @@ static int help(void) {
" --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"
+ " -x --catalog Add message explanations where available\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"
@@ -116,6 +121,8 @@ static int help(void) {
" --header Show journal header information\n"
" --disk-usage Show total disk usage\n"
" -F --field=FIELD List all values a certain field takes\n"
+ " --list-catalog Show message IDs of all entries in the message catalog\n"
+ " --update-catalog Update the message catalog database\n"
#ifdef HAVE_GCRYPT
" --setup-keys Generate new FSS key pair\n"
" --verify Verify journal file consistency\n"
@@ -139,7 +146,9 @@ static int parse_argv(int argc, char *argv[]) {
ARG_VERIFY_KEY,
ARG_DISK_USAGE,
ARG_SINCE,
- ARG_UNTIL
+ ARG_UNTIL,
+ ARG_LIST_CATALOG,
+ ARG_UPDATE_CATALOG
};
static const struct option options[] = {
@@ -168,6 +177,9 @@ static int parse_argv(int argc, char *argv[]) {
{ "until", required_argument, NULL, ARG_UNTIL },
{ "unit", required_argument, NULL, 'u' },
{ "field", required_argument, NULL, 'F' },
+ { "catalog", no_argument, NULL, 'x' },
+ { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG },
+ { "update-catalog",no_argument, NULL, ARG_UPDATE_CATALOG },
{ NULL, 0, NULL, 0 }
};
@@ -176,7 +188,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hfo:an::qmbD:p:c:u:F:", options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "hfo:an::qmbD:p:c:u:F:x", options, NULL)) >= 0) {
switch (c) {
@@ -376,6 +388,18 @@ static int parse_argv(int argc, char *argv[]) {
arg_field = optarg;
break;
+ case 'x':
+ arg_catalog = true;
+ break;
+
+ case ARG_LIST_CATALOG:
+ arg_action = ACTION_LIST_CATALOG;
+ break;
+
+ case ARG_UPDATE_CATALOG:
+ arg_action = ACTION_UPDATE_CATALOG;
+ break;
+
default:
log_error("Unknown option code %c", c);
return -EINVAL;
@@ -841,6 +865,16 @@ int main(int argc, char *argv[]) {
goto finish;
}
+ if (arg_action == ACTION_LIST_CATALOG) {
+ r = catalog_list(stdout);
+ goto finish;
+ }
+
+ if (arg_action == ACTION_UPDATE_CATALOG) {
+ r = catalog_update();
+ goto finish;
+ }
+
r = access_check();
if (r < 0)
goto finish;
@@ -1030,7 +1064,8 @@ int main(int argc, char *argv[]) {
flags =
arg_all * OUTPUT_SHOW_ALL |
(!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH |
- on_tty() * OUTPUT_COLOR;
+ on_tty() * OUTPUT_COLOR |
+ arg_catalog * OUTPUT_CATALOG;
r = output_journal(stdout, j, arg_output, 0, flags);
if (r < 0)
diff --git a/src/journal/libsystemd-journal.sym b/src/journal/libsystemd-journal.sym
index ad78fcc74d..d4b0c32612 100644
--- a/src/journal/libsystemd-journal.sym
+++ b/src/journal/libsystemd-journal.sym
@@ -84,4 +84,5 @@ global:
LIBSYSTEMD_JOURNAL_196 {
global:
sd_journal_fd_reliable;
+ sd_journal_get_catalog;
} LIBSYSTEMD_JOURNAL_195;
diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c
index a346691e21..c86f1eaab1 100644
--- a/src/journal/sd-journal.c
+++ b/src/journal/sd-journal.c
@@ -38,11 +38,15 @@
#include "compress.h"
#include "journal-internal.h"
#include "missing.h"
+#include "catalog.h"
+#include "replace-var.h"
#define JOURNAL_FILES_MAX 1024
#define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC)
+#define REPLACE_VAR_MAX 256
+
static void detach_location(sd_journal *j) {
Iterator i;
JournalFile *f;
@@ -2389,3 +2393,59 @@ _public_ int sd_journal_reliable_fd(sd_journal *j) {
return !j->on_network;
}
+
+static char *lookup_field(const char *field, void *userdata) {
+ sd_journal *j = userdata;
+ const void *data;
+ size_t size, d;
+ int r;
+
+ assert(field);
+ assert(j);
+
+ r = sd_journal_get_data(j, field, &data, &size);
+ if (r < 0 ||
+ size > REPLACE_VAR_MAX)
+ return strdup(field);
+
+ d = strlen(field) + 1;
+
+ return strndup((const char*) data + d, size - d);
+}
+
+_public_ int sd_journal_get_catalog(sd_journal *j, char **ret) {
+ const void *data;
+ size_t size;
+ sd_id128_t id;
+ _cleanup_free_ char *text = NULL, *cid = NULL;
+ char *t;
+ int r;
+
+ if (!j)
+ return -EINVAL;
+ if (!ret)
+ return -EINVAL;
+
+ r = sd_journal_get_data(j, "MESSAGE_ID", &data, &size);
+ if (r < 0)
+ return r;
+
+ cid = strndup((const char*) data + 11, size - 11);
+ if (!cid)
+ return -ENOMEM;
+
+ r = sd_id128_from_string(cid, &id);
+ if (r < 0)
+ return r;
+
+ r = catalog_get(id, &text);
+ if (r < 0)
+ return r;
+
+ t = replace_var(text, lookup_field, j);
+ if (!t)
+ return -ENOMEM;
+
+ *ret = t;
+ return 0;
+}
diff --git a/src/journal/test-catalog.c b/src/journal/test-catalog.c
new file mode 100644
index 0000000000..cec8a11c43
--- /dev/null
+++ b/src/journal/test-catalog.c
@@ -0,0 +1,48 @@
+/*-*- 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 <locale.h>
+
+#include "util.h"
+#include "log.h"
+#include "catalog.h"
+#include "sd-messages.h"
+
+int main(int argc, char *argv[]) {
+
+ _cleanup_free_ char *text = NULL;
+
+ setlocale(LC_ALL, "de_DE.UTF-8");
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(catalog_update() >= 0);
+
+ assert_se(catalog_list(stdout) >= 0);
+
+ assert_se(catalog_get(SD_MESSAGE_COREDUMP, &text) >= 0);
+
+ printf(">>>%s<<<\n", text);
+
+ fflush(stdout);
+
+ return 0;
+}
diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c
index 36cce73550..cb93761bd1 100644
--- a/src/shared/logs-show.c
+++ b/src/shared/logs-show.c
@@ -34,6 +34,26 @@
#define PRINT_THRESHOLD 128
#define JSON_THRESHOLD 4096
+static int print_catalog(FILE *f, sd_journal *j) {
+ int r;
+ _cleanup_free_ char *t = NULL, *z = NULL;
+
+
+ r = sd_journal_get_catalog(j, &t);
+ if (r < 0)
+ return r;
+
+ z = strreplace(strstrip(t), "\n", "\n-- ");
+ if (!z)
+ return log_oom();
+
+ fputs("-- ", f);
+ fputs(z, f);
+ fputc('\n', f);
+
+ return 0;
+}
+
static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
size_t fl, nl;
void *buf;
@@ -265,6 +285,9 @@ static int output_short(
} else
fputs("\n", f);
+ if (flags & OUTPUT_CATALOG)
+ print_catalog(f, j);
+
return 0;
}
@@ -322,6 +345,9 @@ static int output_verbose(
fprintf(f, "\t%.*s\n", (int) length, (const char*) data);
}
+ if (flags & OUTPUT_CATALOG)
+ print_catalog(f, j);
+
return 0;
}
diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h
index 06082800c8..11cb41aab3 100644
--- a/src/shared/logs-show.h
+++ b/src/shared/logs-show.h
@@ -45,7 +45,8 @@ typedef enum OutputFlags {
OUTPUT_FOLLOW = 1 << 1,
OUTPUT_WARN_CUTOFF = 1 << 2,
OUTPUT_FULL_WIDTH = 1 << 3,
- OUTPUT_COLOR = 1 << 4
+ OUTPUT_COLOR = 1 << 4,
+ OUTPUT_CATALOG = 1 << 5
} OutputFlags;
int output_journal(
diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h
index 7241173035..f9919b29f1 100644
--- a/src/systemd/sd-journal.h
+++ b/src/systemd/sd-journal.h
@@ -128,6 +128,8 @@ int sd_journal_reliable_fd(sd_journal *j);
int sd_journal_process(sd_journal *j);
int sd_journal_wait(sd_journal *j, uint64_t timeout_usec);
+int sd_journal_get_catalog(sd_journal *j, char **text);
+
#define SD_JOURNAL_FOREACH(j) \
if (sd_journal_seek_head(j) >= 0) \
while (sd_journal_next(j) > 0)