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 @@ -232,6 +232,25 @@ even a timestamp. + + + + + 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. + + @@ -404,6 +423,26 @@ journal files. + + + + List the contents of + the message catalog, as table of + message IDs plus their short + description strings. + + + + + + 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. + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#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 . +***/ + +#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 . +***/ + +#include + +#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)