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);
128 strscpy(i->language, sizeof(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");
142 int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
143 _cleanup_fclose_ FILE *f = NULL;
144 _cleanup_free_ char *payload = NULL;
148 bool got_id = false, empty_line = true;
155 f = fopen(path, "re");
157 log_error("Failed to open file %s: %m", path);
166 if (!fgets(line, sizeof(line), f)) {
170 log_error("Failed to read file %s: %m", path);
183 if (strchr(COMMENTS "\n", line[0]))
187 strlen(line) >= 2+1+32 &&
191 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
198 with_language = line[2+1+32] != '\0';
201 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
204 r = finish_item(h, sb, id, language, payload);
210 t = strstrip(line + 2 + 1 + 32 + 1);
214 log_error("[%s:%u] Language too short.", path, n);
217 if (c > sizeof(language) - 1) {
218 log_error("[%s:%u] language too long.", path, n);
222 strscpy(language, sizeof(language), t);
239 log_error("[%s:%u] Got payload before ID.", path, n);
243 a = payload ? strlen(payload) : 0;
246 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
247 t = realloc(payload, c);
253 memcpy(t + a + 1, line, b);
257 memcpy(t + a, line, b);
267 r = finish_item(h, sb, id, language, payload);
275 static long write_catalog(const char *database, Hashmap *h, struct strbuf *sb,
276 CatalogItem *items, size_t n) {
277 CatalogHeader header;
278 _cleanup_fclose_ FILE *w = NULL;
280 _cleanup_free_ char *d, *p = NULL;
283 d = dirname_malloc(database);
287 r = mkdir_p(d, 0775);
289 log_error("Recursive mkdir %s: %s", d, strerror(-r));
293 r = fopen_temporary(database, &w, &p);
295 log_error("Failed to open database for writing: %s: %s",
296 database, strerror(-r));
301 memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
302 header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
303 header.catalog_item_size = htole64(sizeof(CatalogItem));
304 header.n_items = htole64(hashmap_size(h));
308 k = fwrite(&header, 1, sizeof(header), w);
309 if (k != sizeof(header)) {
310 log_error("%s: failed to write header.", p);
314 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
315 if (k != n * sizeof(CatalogItem)) {
316 log_error("%s: failed to write database.", p);
320 k = fwrite(sb->buf, 1, sb->len, w);
322 log_error("%s: failed to write strings.", p);
329 log_error("%s: failed to write database.", p);
333 fchmod(fileno(w), 0644);
335 if (rename(p, database) < 0) {
336 log_error("rename (%s -> %s) failed: %m", p, database);
348 int catalog_update(const char* database, const char* root, const char* const* dirs) {
349 _cleanup_strv_free_ char **files = NULL;
352 struct strbuf *sb = NULL;
353 _cleanup_free_ CatalogItem *items = NULL;
359 h = hashmap_new(catalog_hash_func, catalog_compare_func);
367 r = conf_files_list_strv(&files, ".catalog", root, dirs);
369 log_error("Failed to get catalog files: %s", strerror(-r));
373 STRV_FOREACH(f, files) {
374 log_debug("reading file '%s'", *f);
375 catalog_import_file(h, sb, *f);
378 if (hashmap_size(h) <= 0) {
379 log_info("No items in catalog.");
383 log_debug("Found %u items in catalog.", hashmap_size(h));
387 items = new(CatalogItem, hashmap_size(h));
394 HASHMAP_FOREACH(i, h, j) {
395 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
396 SD_ID128_FORMAT_VAL(i->id),
397 isempty(i->language) ? "C" : i->language);
401 assert(n == hashmap_size(h));
402 qsort(items, n, sizeof(CatalogItem), catalog_compare_func);
404 r = write_catalog(database, h, sb, items, n);
406 log_error("Failed to write %s: %s", database, strerror(-r));
408 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
409 database, n, sb->len, r);
415 hashmap_free_free(h);
419 return r < 0 ? r : 0;
422 static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
423 const CatalogHeader *h;
432 fd = open(database, O_RDONLY|O_CLOEXEC);
436 if (fstat(fd, &st) < 0) {
437 close_nointr_nofail(fd);
441 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
442 close_nointr_nofail(fd);
446 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
447 if (p == MAP_FAILED) {
448 close_nointr_nofail(fd);
453 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
454 le64toh(h->header_size) < sizeof(CatalogHeader) ||
455 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
456 h->incompatible_flags != 0 ||
457 le64toh(h->n_items) <= 0 ||
458 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
459 close_nointr_nofail(fd);
460 munmap(p, st.st_size);
471 static const char *find_id(void *p, sd_id128_t id) {
472 CatalogItem key, *f = NULL;
473 const CatalogHeader *h = p;
479 loc = setlocale(LC_MESSAGES, NULL);
480 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
481 strncpy(key.language, loc, sizeof(key.language));
482 key.language[strcspn(key.language, ".@")] = 0;
484 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
488 e = strchr(key.language, '_');
491 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
498 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
504 return (const char*) p +
505 le64toh(h->header_size) +
506 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
510 int catalog_get(const char* database, sd_id128_t id, char **_text) {
511 _cleanup_close_ int fd = -1;
520 r = open_mmap(database, &fd, &st, &p);
541 munmap(p, st.st_size);
546 static char *find_header(const char *s, const char *header) {
551 v = startswith(s, header);
553 v += strspn(v, WHITESPACE);
554 return strndup(v, strcspn(v, NEWLINE));
570 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
572 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
574 subject = find_header(s, "Subject:");
575 defined_by = find_header(s, "Defined-By:");
577 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
578 SD_ID128_FORMAT_VAL(id),
579 strna(defined_by), strna(subject));
581 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
582 SD_ID128_FORMAT_VAL(id), s);
586 int catalog_list(FILE *f, const char *database, bool oneline) {
587 _cleanup_close_ int fd = -1;
590 const CatalogHeader *h;
591 const CatalogItem *items;
595 bool last_id_set = false;
597 r = open_mmap(database, &fd, &st, &p);
602 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
604 for (n = 0; n < le64toh(h->n_items); n++) {
607 if (last_id_set && sd_id128_equal(last_id, items[n].id))
610 assert_se(s = find_id(p, items[n].id));
612 dump_catalog_entry(f, items[n].id, s, oneline);
615 last_id = items[n].id;
618 munmap(p, st.st_size);
623 int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
627 STRV_FOREACH(item, items) {
630 _cleanup_free_ char *msg = NULL;
632 k = sd_id128_from_string(*item, &id);
634 log_error("Failed to parse id128 '%s': %s",
635 *item, strerror(-k));
641 k = catalog_get(database, id, &msg);
643 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
644 "Failed to retrieve catalog entry for '%s': %s",
645 *item, strerror(-k));
651 dump_catalog_entry(f, id, msg, oneline);