journal: implement message catalog

The message catalog can be used to attach short help texts to log lines,
keyed by their MESSAGE_ID= fields. This is useful to help the
administrator understand the context and cause of a message, find
possible solutions and find further related documentation.

Since this is keyed off MESSAGE_ID= this will only work for native
journal messages.

The message catalog supports i18n, and is useful to augment english
language system messages with explanations in the local language.

This commit only includes short explanatory messages for a few example
message IDs, we'll add more complete documentation for the relevant
systemd messages later on.
This commit is contained in:
Lennart Poettering 2012-11-15 23:03:31 +01:00
parent 59f432ea6d
commit d4205751d4
13 changed files with 952 additions and 9 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/test-catalog
/test-replace-var
/test-journal-enum
/test-sleep

View File

@ -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 \

99
catalog/systemd.catalog Normal file
View File

@ -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.

View File

@ -232,6 +232,25 @@
even a timestamp.</para></listitem>
</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>
@ -404,6 +423,26 @@
journal files.</para></listitem>
</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>

584
src/journal/catalog.c Normal file
View File

@ -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;
}

28
src/journal/catalog.h Normal file
View File

@ -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);

View File

@ -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)

View File

@ -84,4 +84,5 @@ global:
LIBSYSTEMD_JOURNAL_196 {
global:
sd_journal_fd_reliable;
sd_journal_get_catalog;
} LIBSYSTEMD_JOURNAL_195;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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(

View File

@ -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)