diff options
Diffstat (limited to 'src/journal')
-rw-r--r-- | src/journal/catalog.c | 584 | ||||
-rw-r--r-- | src/journal/catalog.h | 28 | ||||
-rw-r--r-- | src/journal/journalctl.c | 41 | ||||
-rw-r--r-- | src/journal/libsystemd-journal.sym | 1 | ||||
-rw-r--r-- | src/journal/sd-journal.c | 60 | ||||
-rw-r--r-- | src/journal/test-catalog.c | 48 |
6 files changed, 759 insertions, 3 deletions
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; +} |