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/>.
33 #include "sparse-endian.h"
39 #include "conf-files.h"
43 const char * const catalog_file_dirs[] = {
44 "/usr/local/lib/systemd/catalog/",
45 "/usr/lib/systemd/catalog/",
49 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
51 typedef struct CatalogHeader {
52 uint8_t signature[8]; /* "RHHHKSLP" */
53 le32_t compatible_flags;
54 le32_t incompatible_flags;
57 le64_t catalog_item_size;
60 typedef struct CatalogItem {
66 unsigned catalog_hash_func(const void *p) {
67 const CatalogItem *i = p;
69 assert_cc(sizeof(unsigned) == sizeof(uint8_t)*4);
71 return (((unsigned) i->id.bytes[0] << 24) |
72 ((unsigned) i->id.bytes[1] << 16) |
73 ((unsigned) i->id.bytes[2] << 8) |
74 ((unsigned) i->id.bytes[3])) ^
75 (((unsigned) i->id.bytes[4] << 24) |
76 ((unsigned) i->id.bytes[5] << 16) |
77 ((unsigned) i->id.bytes[6] << 8) |
78 ((unsigned) i->id.bytes[7])) ^
79 (((unsigned) i->id.bytes[8] << 24) |
80 ((unsigned) i->id.bytes[9] << 16) |
81 ((unsigned) i->id.bytes[10] << 8) |
82 ((unsigned) i->id.bytes[11])) ^
83 (((unsigned) i->id.bytes[12] << 24) |
84 ((unsigned) i->id.bytes[13] << 16) |
85 ((unsigned) i->id.bytes[14] << 8) |
86 ((unsigned) i->id.bytes[15])) ^
87 string_hash_func(i->language);
90 int catalog_compare_func(const void *a, const void *b) {
91 const CatalogItem *i = a, *j = b;
94 for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) {
95 if (i->id.bytes[k] < j->id.bytes[k])
97 if (i->id.bytes[k] > j->id.bytes[k])
101 return strcmp(i->language, j->language);
104 static int finish_item(
108 const char *language,
109 const char *payload) {
119 offset = strbuf_add_string(sb, payload, strlen(payload));
123 i = new0(CatalogItem, 1);
129 assert(strlen(language) > 1 && strlen(language) < 32);
130 strcpy(i->language, language);
132 i->offset = htole64((uint64_t) offset);
134 r = hashmap_put(h, i, i);
136 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.",
137 SD_ID128_FORMAT_VAL(id), language ? language : "C");
145 int catalog_file_lang(const char* filename, char **lang) {
146 char *beg, *end, *_lang;
148 end = endswith(filename, ".catalog");
153 while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32)
156 if (*beg != '.' || end <= beg + 1)
159 _lang = strndup(beg + 1, end - beg - 1);
167 int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
168 _cleanup_fclose_ FILE *f = NULL;
169 _cleanup_free_ char *payload = NULL;
172 _cleanup_free_ char *deflang = NULL, *lang = NULL;
173 bool got_id = false, empty_line = true;
180 f = fopen(path, "re");
182 log_error("Failed to open file %s: %m", path);
186 r = catalog_file_lang(path, &deflang);
188 log_error("Failed to determine language for file %s: %m", path);
190 log_debug("File %s has language %s.", path, deflang);
197 if (!fgets(line, sizeof(line), f)) {
201 log_error("Failed to read file %s: %m", path);
214 if (strchr(COMMENTS "\n", line[0]))
218 strlen(line) >= 2+1+32 &&
222 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
229 with_language = line[2+1+32] != '\0';
232 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
235 r = finish_item(h, sb, id, lang ?: deflang, payload);
244 t = strstrip(line + 2 + 1 + 32 + 1);
248 log_error("[%s:%u] Language too short.", path, n);
252 log_error("[%s:%u] language too long.", path, n);
257 log_warning("[%s:%u] language %s", path, n,
259 "specified unnecessarily" :
260 "differs from default for file");
280 log_error("[%s:%u] Got payload before ID.", path, n);
284 a = payload ? strlen(payload) : 0;
287 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
288 t = realloc(payload, c);
294 memcpy(t + a + 1, line, b);
298 memcpy(t + a, line, b);
308 r = finish_item(h, sb, id, lang ?: deflang, payload);
316 static long write_catalog(const char *database, Hashmap *h, struct strbuf *sb,
317 CatalogItem *items, size_t n) {
318 CatalogHeader header;
319 _cleanup_fclose_ FILE *w = NULL;
321 _cleanup_free_ char *d, *p = NULL;
324 d = dirname_malloc(database);
328 r = mkdir_p(d, 0775);
330 log_error("Recursive mkdir %s: %s", d, strerror(-r));
334 r = fopen_temporary(database, &w, &p);
336 log_error("Failed to open database for writing: %s: %s",
337 database, strerror(-r));
342 memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
343 header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
344 header.catalog_item_size = htole64(sizeof(CatalogItem));
345 header.n_items = htole64(hashmap_size(h));
349 k = fwrite(&header, 1, sizeof(header), w);
350 if (k != sizeof(header)) {
351 log_error("%s: failed to write header.", p);
355 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
356 if (k != n * sizeof(CatalogItem)) {
357 log_error("%s: failed to write database.", p);
361 k = fwrite(sb->buf, 1, sb->len, w);
363 log_error("%s: failed to write strings.", p);
370 log_error("%s: failed to write database.", p);
374 fchmod(fileno(w), 0644);
376 if (rename(p, database) < 0) {
377 log_error("rename (%s -> %s) failed: %m", p, database);
389 int catalog_update(const char* database, const char* root, const char* const* dirs) {
390 _cleanup_strv_free_ char **files = NULL;
393 struct strbuf *sb = NULL;
394 _cleanup_free_ CatalogItem *items = NULL;
400 h = hashmap_new(catalog_hash_func, catalog_compare_func);
408 r = conf_files_list_strv(&files, ".catalog", root, dirs);
410 log_error("Failed to get catalog files: %s", strerror(-r));
414 STRV_FOREACH(f, files) {
415 log_debug("reading file '%s'", *f);
416 catalog_import_file(h, sb, *f);
419 if (hashmap_size(h) <= 0) {
420 log_info("No items in catalog.");
424 log_debug("Found %u items in catalog.", hashmap_size(h));
428 items = new(CatalogItem, hashmap_size(h));
435 HASHMAP_FOREACH(i, h, j) {
436 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
437 SD_ID128_FORMAT_VAL(i->id),
438 isempty(i->language) ? "C" : i->language);
442 assert(n == hashmap_size(h));
443 qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func);
445 r = write_catalog(database, h, sb, items, n);
447 log_error("Failed to write %s: %s", database, strerror(-r));
449 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
450 database, n, sb->len, r);
456 hashmap_free_free(h);
460 return r < 0 ? r : 0;
463 static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
464 const CatalogHeader *h;
473 fd = open(database, O_RDONLY|O_CLOEXEC);
477 if (fstat(fd, &st) < 0) {
478 close_nointr_nofail(fd);
482 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
483 close_nointr_nofail(fd);
487 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
488 if (p == MAP_FAILED) {
489 close_nointr_nofail(fd);
494 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
495 le64toh(h->header_size) < sizeof(CatalogHeader) ||
496 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
497 h->incompatible_flags != 0 ||
498 le64toh(h->n_items) <= 0 ||
499 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
500 close_nointr_nofail(fd);
501 munmap(p, st.st_size);
512 static const char *find_id(void *p, sd_id128_t id) {
513 CatalogItem key, *f = NULL;
514 const CatalogHeader *h = p;
520 loc = setlocale(LC_MESSAGES, NULL);
521 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
522 strncpy(key.language, loc, sizeof(key.language));
523 key.language[strcspn(key.language, ".@")] = 0;
525 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
529 e = strchr(key.language, '_');
532 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
539 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
545 return (const char*) p +
546 le64toh(h->header_size) +
547 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
551 int catalog_get(const char* database, sd_id128_t id, char **_text) {
552 _cleanup_close_ int fd = -1;
561 r = open_mmap(database, &fd, &st, &p);
582 munmap(p, st.st_size);
587 static char *find_header(const char *s, const char *header) {
592 v = startswith(s, header);
594 v += strspn(v, WHITESPACE);
595 return strndup(v, strcspn(v, NEWLINE));
611 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
613 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
615 subject = find_header(s, "Subject:");
616 defined_by = find_header(s, "Defined-By:");
618 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
619 SD_ID128_FORMAT_VAL(id),
620 strna(defined_by), strna(subject));
622 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
623 SD_ID128_FORMAT_VAL(id), s);
627 int catalog_list(FILE *f, const char *database, bool oneline) {
628 _cleanup_close_ int fd = -1;
631 const CatalogHeader *h;
632 const CatalogItem *items;
636 bool last_id_set = false;
638 r = open_mmap(database, &fd, &st, &p);
643 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
645 for (n = 0; n < le64toh(h->n_items); n++) {
648 if (last_id_set && sd_id128_equal(last_id, items[n].id))
651 assert_se(s = find_id(p, items[n].id));
653 dump_catalog_entry(f, items[n].id, s, oneline);
656 last_id = items[n].id;
659 munmap(p, st.st_size);
664 int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
668 STRV_FOREACH(item, items) {
671 _cleanup_free_ char *msg = NULL;
673 k = sd_id128_from_string(*item, &id);
675 log_error("Failed to parse id128 '%s': %s",
676 *item, strerror(-k));
682 k = catalog_get(database, id, &msg);
684 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
685 "Failed to retrieve catalog entry for '%s': %s",
686 *item, strerror(-k));
692 dump_catalog_entry(f, id, msg, oneline);