1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
32 #include "sparse-endian.h"
37 #include "conf-files.h"
40 #include "siphash24.h"
42 const char * const catalog_file_dirs[] = {
43 "/usr/local/lib/systemd/catalog/",
44 "/usr/lib/systemd/catalog/",
48 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
50 typedef struct CatalogHeader {
51 uint8_t signature[8]; /* "RHHHKSLP" */
52 le32_t compatible_flags;
53 le32_t incompatible_flags;
56 le64_t catalog_item_size;
59 typedef struct CatalogItem {
65 static unsigned long catalog_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) {
66 const CatalogItem *i = p;
71 l = strlen(i->language);
72 sz = sizeof(i->id) + l;
75 memcpy(mempcpy(v, &i->id, sizeof(i->id)), i->language, l);
77 siphash24((uint8_t*) &u, v, sz, hash_key);
79 return (unsigned long) u;
82 static int catalog_compare_func(const void *a, const void *b) {
83 const CatalogItem *i = a, *j = b;
86 for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) {
87 if (i->id.bytes[k] < j->id.bytes[k])
89 if (i->id.bytes[k] > j->id.bytes[k])
93 return strcmp(i->language, j->language);
96 const struct hash_ops catalog_hash_ops = {
97 .hash = catalog_hash_func,
98 .compare = catalog_compare_func
101 static int finish_item(
105 const char *language,
106 const char *payload) {
109 _cleanup_free_ CatalogItem *i = NULL;
116 offset = strbuf_add_string(sb, payload, strlen(payload));
120 i = new0(CatalogItem, 1);
126 assert(strlen(language) > 1 && strlen(language) < 32);
127 strcpy(i->language, language);
129 i->offset = htole64((uint64_t) offset);
131 r = hashmap_put(h, i, i);
133 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.",
134 SD_ID128_FORMAT_VAL(id), language ? language : "C");
143 int catalog_file_lang(const char* filename, char **lang) {
144 char *beg, *end, *_lang;
146 end = endswith(filename, ".catalog");
151 while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32)
154 if (*beg != '.' || end <= beg + 1)
157 _lang = strndup(beg + 1, end - beg - 1);
165 static int catalog_entry_lang(const char* filename, int line,
166 const char* t, const char* deflang, char **lang) {
171 log_error("[%s:%u] Language too short.", filename, line);
175 log_error("[%s:%u] language too long.", filename, line);
180 if (streq(t, deflang)) {
181 log_warning("[%s:%u] language specified unnecessarily",
185 log_warning("[%s:%u] language differs from default for file",
196 int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
197 _cleanup_fclose_ FILE *f = NULL;
198 _cleanup_free_ char *payload = NULL;
201 _cleanup_free_ char *deflang = NULL, *lang = NULL;
202 bool got_id = false, empty_line = true;
209 f = fopen(path, "re");
211 return log_error_errno(errno, "Failed to open file %s: %m", path);
213 r = catalog_file_lang(path, &deflang);
215 log_error_errno(errno, "Failed to determine language for file %s: %m", path);
217 log_debug("File %s has language %s.", path, deflang);
224 if (!fgets(line, sizeof(line), f)) {
228 log_error_errno(errno, "Failed to read file %s: %m", path);
241 if (strchr(COMMENTS "\n", line[0]))
245 strlen(line) >= 2+1+32 &&
249 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
256 with_language = line[2+1+32] != '\0';
259 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
262 r = finish_item(h, sb, id, lang ?: deflang, payload);
271 t = strstrip(line + 2 + 1 + 32 + 1);
273 r = catalog_entry_lang(path, n, t, deflang, &lang);
291 log_error("[%s:%u] Got payload before ID.", path, n);
295 a = payload ? strlen(payload) : 0;
298 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
299 t = realloc(payload, c);
305 memcpy(t + a + 1, line, b);
309 memcpy(t + a, line, b);
319 r = finish_item(h, sb, id, lang ?: deflang, payload);
327 static long write_catalog(const char *database, Hashmap *h, struct strbuf *sb,
328 CatalogItem *items, size_t n) {
329 CatalogHeader header;
330 _cleanup_fclose_ FILE *w = NULL;
332 _cleanup_free_ char *d, *p = NULL;
335 d = dirname_malloc(database);
339 r = mkdir_p(d, 0775);
341 return log_error_errno(r, "Recursive mkdir %s: %m", d);
343 r = fopen_temporary(database, &w, &p);
345 return log_error_errno(r, "Failed to open database for writing: %s: %m",
349 memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
350 header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
351 header.catalog_item_size = htole64(sizeof(CatalogItem));
352 header.n_items = htole64(hashmap_size(h));
356 k = fwrite(&header, 1, sizeof(header), w);
357 if (k != sizeof(header)) {
358 log_error("%s: failed to write header.", p);
362 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
363 if (k != n * sizeof(CatalogItem)) {
364 log_error("%s: failed to write database.", p);
368 k = fwrite(sb->buf, 1, sb->len, w);
370 log_error("%s: failed to write strings.", p);
377 log_error("%s: failed to write database.", p);
381 fchmod(fileno(w), 0644);
383 if (rename(p, database) < 0) {
384 log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database);
396 int catalog_update(const char* database, const char* root, const char* const* dirs) {
397 _cleanup_strv_free_ char **files = NULL;
399 struct strbuf *sb = NULL;
400 _cleanup_hashmap_free_free_ Hashmap *h = NULL;
401 _cleanup_free_ CatalogItem *items = NULL;
407 h = hashmap_new(&catalog_hash_ops);
415 r = conf_files_list_strv(&files, ".catalog", root, dirs);
417 log_error_errno(r, "Failed to get catalog files: %m");
421 STRV_FOREACH(f, files) {
422 log_debug("Reading file '%s'", *f);
423 r = catalog_import_file(h, sb, *f);
425 log_error("Failed to import file '%s': %s.",
431 if (hashmap_size(h) <= 0) {
432 log_info("No items in catalog.");
435 log_debug("Found %u items in catalog.", hashmap_size(h));
439 items = new(CatalogItem, hashmap_size(h));
446 HASHMAP_FOREACH(i, h, j) {
447 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
448 SD_ID128_FORMAT_VAL(i->id),
449 isempty(i->language) ? "C" : i->language);
453 assert(n == hashmap_size(h));
454 qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func);
456 r = write_catalog(database, h, sb, items, n);
458 log_error_errno(r, "Failed to write %s: %m", database);
460 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
461 database, n, sb->len, r);
467 return r < 0 ? r : 0;
470 static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
471 const CatalogHeader *h;
480 fd = open(database, O_RDONLY|O_CLOEXEC);
484 if (fstat(fd, &st) < 0) {
489 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
494 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
495 if (p == MAP_FAILED) {
501 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
502 le64toh(h->header_size) < sizeof(CatalogHeader) ||
503 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
504 h->incompatible_flags != 0 ||
505 le64toh(h->n_items) <= 0 ||
506 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
508 munmap(p, st.st_size);
519 static const char *find_id(void *p, sd_id128_t id) {
520 CatalogItem key, *f = NULL;
521 const CatalogHeader *h = p;
527 loc = setlocale(LC_MESSAGES, NULL);
528 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
529 strncpy(key.language, loc, sizeof(key.language));
530 key.language[strcspn(key.language, ".@")] = 0;
532 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
536 e = strchr(key.language, '_');
539 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
546 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
552 return (const char*) p +
553 le64toh(h->header_size) +
554 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
558 int catalog_get(const char* database, sd_id128_t id, char **_text) {
559 _cleanup_close_ int fd = -1;
568 r = open_mmap(database, &fd, &st, &p);
589 munmap(p, st.st_size);
594 static char *find_header(const char *s, const char *header) {
599 v = startswith(s, header);
601 v += strspn(v, WHITESPACE);
602 return strndup(v, strcspn(v, NEWLINE));
618 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
620 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
622 subject = find_header(s, "Subject:");
623 defined_by = find_header(s, "Defined-By:");
625 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
626 SD_ID128_FORMAT_VAL(id),
627 strna(defined_by), strna(subject));
629 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
630 SD_ID128_FORMAT_VAL(id), s);
634 int catalog_list(FILE *f, const char *database, bool oneline) {
635 _cleanup_close_ int fd = -1;
638 const CatalogHeader *h;
639 const CatalogItem *items;
643 bool last_id_set = false;
645 r = open_mmap(database, &fd, &st, &p);
650 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
652 for (n = 0; n < le64toh(h->n_items); n++) {
655 if (last_id_set && sd_id128_equal(last_id, items[n].id))
658 assert_se(s = find_id(p, items[n].id));
660 dump_catalog_entry(f, items[n].id, s, oneline);
663 last_id = items[n].id;
666 munmap(p, st.st_size);
671 int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
675 STRV_FOREACH(item, items) {
678 _cleanup_free_ char *msg = NULL;
680 k = sd_id128_from_string(*item, &id);
682 log_error_errno(k, "Failed to parse id128 '%s': %m",
689 k = catalog_get(database, id, &msg);
691 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
692 "Failed to retrieve catalog entry for '%s': %s",
693 *item, strerror(-k));
699 dump_catalog_entry(f, id, msg, oneline);