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"
38 #include "conf-files.h"
42 static const char * const conf_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 catalog_hash_func(const void *p) {
66 const CatalogItem *i = p;
68 assert_cc(sizeof(unsigned) == sizeof(uint8_t)*4);
70 return (((unsigned) i->id.bytes[0] << 24) |
71 ((unsigned) i->id.bytes[1] << 16) |
72 ((unsigned) i->id.bytes[2] << 8) |
73 ((unsigned) i->id.bytes[3])) ^
74 (((unsigned) i->id.bytes[4] << 24) |
75 ((unsigned) i->id.bytes[5] << 16) |
76 ((unsigned) i->id.bytes[6] << 8) |
77 ((unsigned) i->id.bytes[7])) ^
78 (((unsigned) i->id.bytes[8] << 24) |
79 ((unsigned) i->id.bytes[9] << 16) |
80 ((unsigned) i->id.bytes[10] << 8) |
81 ((unsigned) i->id.bytes[11])) ^
82 (((unsigned) i->id.bytes[12] << 24) |
83 ((unsigned) i->id.bytes[13] << 16) |
84 ((unsigned) i->id.bytes[14] << 8) |
85 ((unsigned) i->id.bytes[15])) ^
86 string_hash_func(i->language);
89 static int catalog_compare_func(const void *a, const void *b) {
90 const CatalogItem *i = a, *j = b;
93 for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) {
94 if (i->id.bytes[k] < j->id.bytes[k])
96 if (i->id.bytes[k] > j->id.bytes[k])
100 return strcmp(i->language, j->language);
103 static int finish_item(
107 const char *language,
108 const char *payload) {
118 offset = strbuf_add_string(sb, payload, strlen(payload));
122 i = new0(CatalogItem, 1);
127 strscpy(i->language, sizeof(i->language), language);
128 i->offset = htole64((uint64_t) offset);
130 r = hashmap_put(h, i, i);
132 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.",
133 SD_ID128_FORMAT_VAL(id), language ? language : "C");
141 static int import_file(Hashmap *h, struct strbuf *sb, const char *path) {
142 _cleanup_fclose_ FILE *f = NULL;
143 _cleanup_free_ char *payload = NULL;
147 bool got_id = false, empty_line = true;
154 f = fopen(path, "re");
156 log_error("Failed to open file %s: %m", path);
165 if (!fgets(line, sizeof(line), f)) {
169 log_error("Failed to read file %s: %m", path);
182 if (strchr(COMMENTS, line[0]))
186 strlen(line) >= 2+1+32 &&
190 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
197 with_language = line[2+1+32] != '\0';
200 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
203 r = finish_item(h, sb, id, language, payload);
209 t = strstrip(line + 2 + 1 + 32 + 1);
213 log_error("[%s:%u] Language too short.", path, n);
216 if (c > sizeof(language) - 1) {
217 log_error("[%s:%u] language too long.", path, n);
221 strscpy(language, sizeof(language), t);
238 log_error("[%s:%u] Got payload before ID.", path, n);
242 a = payload ? strlen(payload) : 0;
245 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
246 t = realloc(payload, c);
252 memcpy(t + a + 1, line, b);
256 memcpy(t + a, line, b);
266 r = finish_item(h, sb, id, language, payload);
274 #define CATALOG_DATABASE CATALOG_PATH "/database"
276 int catalog_update(void) {
277 _cleanup_strv_free_ char **files = NULL;
278 _cleanup_fclose_ FILE *w = NULL;
279 _cleanup_free_ char *p = NULL;
282 struct strbuf *sb = NULL;
283 _cleanup_free_ CatalogItem *items = NULL;
285 CatalogHeader header;
291 h = hashmap_new(catalog_hash_func, catalog_compare_func);
301 r = conf_files_list_strv(&files, ".catalog", NULL, (const char **) conf_file_dirs);
303 log_error("Failed to get catalog files: %s", strerror(-r));
307 STRV_FOREACH(f, files) {
308 log_debug("reading file '%s'", *f);
309 import_file(h, sb, *f);
312 if (hashmap_size(h) <= 0) {
313 log_info("No items in catalog.");
317 log_debug("Found %u items in catalog.", hashmap_size(h));
321 items = new(CatalogItem, hashmap_size(h));
328 HASHMAP_FOREACH(i, h, j) {
329 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
330 SD_ID128_FORMAT_VAL(i->id),
331 isempty(i->language) ? "C" : i->language);
335 assert(n == hashmap_size(h));
336 qsort(items, n, sizeof(CatalogItem), catalog_compare_func);
338 r = mkdir_p(CATALOG_PATH, 0775);
340 log_error("Recursive mkdir %s: %s", CATALOG_PATH, strerror(-r));
344 r = fopen_temporary(CATALOG_DATABASE, &w, &p);
346 log_error("Failed to open database for writing: %s: %s",
347 CATALOG_DATABASE, strerror(-r));
352 memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
353 header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
354 header.catalog_item_size = htole64(sizeof(CatalogItem));
355 header.n_items = htole64(hashmap_size(h));
357 k = fwrite(&header, 1, sizeof(header), w);
358 if (k != sizeof(header)) {
359 log_error("%s: failed to write header.", p);
363 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
364 if (k != n * sizeof(CatalogItem)) {
365 log_error("%s: failed to write database.", p);
369 k = fwrite(sb->buf, 1, sb->len, w);
371 log_error("%s: failed to write strings.", p);
378 log_error("%s: failed to write database.", p);
382 fchmod(fileno(w), 0644);
384 if (rename(p, CATALOG_DATABASE) < 0) {
385 log_error("rename (%s -> %s) failed: %m", p, CATALOG_DATABASE);
390 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
391 CATALOG_DATABASE, n, sb->len, ftell(w));
399 hashmap_free_free(h);
410 static int open_mmap(int *_fd, struct stat *_st, void **_p) {
411 const CatalogHeader *h;
420 fd = open(CATALOG_DATABASE, O_RDONLY|O_CLOEXEC);
424 if (fstat(fd, &st) < 0) {
425 close_nointr_nofail(fd);
429 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
430 close_nointr_nofail(fd);
434 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
435 if (p == MAP_FAILED) {
436 close_nointr_nofail(fd);
441 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
442 le64toh(h->header_size) < sizeof(CatalogHeader) ||
443 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
444 h->incompatible_flags != 0 ||
445 le64toh(h->n_items) <= 0 ||
446 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
447 close_nointr_nofail(fd);
448 munmap(p, st.st_size);
459 static const char *find_id(void *p, sd_id128_t id) {
460 CatalogItem key, *f = NULL;
461 const CatalogHeader *h = p;
467 loc = setlocale(LC_MESSAGES, NULL);
468 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
469 strncpy(key.language, loc, sizeof(key.language));
470 key.language[strcspn(key.language, ".@")] = 0;
472 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
476 e = strchr(key.language, '_');
479 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
486 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
492 return (const char*) p +
493 le64toh(h->header_size) +
494 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
498 int catalog_get(sd_id128_t id, char **_text) {
499 _cleanup_close_ int fd = -1;
508 r = open_mmap(&fd, &st, &p);
529 munmap(p, st.st_size);
534 static char *find_header(const char *s, const char *header) {
539 v = startswith(s, header);
541 v += strspn(v, WHITESPACE);
542 return strndup(v, strcspn(v, NEWLINE));
558 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
560 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
562 subject = find_header(s, "Subject:");
563 defined_by = find_header(s, "Defined-By:");
565 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
566 SD_ID128_FORMAT_VAL(id),
567 strna(defined_by), strna(subject));
569 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
570 SD_ID128_FORMAT_VAL(id), s);
574 int catalog_list(FILE *f, bool oneline) {
575 _cleanup_close_ int fd = -1;
578 const CatalogHeader *h;
579 const CatalogItem *items;
583 bool last_id_set = false;
585 r = open_mmap(&fd, &st, &p);
590 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
592 for (n = 0; n < le64toh(h->n_items); n++) {
595 if (last_id_set && sd_id128_equal(last_id, items[n].id))
598 assert_se(s = find_id(p, items[n].id));
600 dump_catalog_entry(f, items[n].id, s, oneline);
603 last_id = items[n].id;
606 munmap(p, st.st_size);
611 int catalog_list_items(FILE *f, bool oneline, char **items) {
615 STRV_FOREACH(item, items) {
618 char _cleanup_free_ *msg = NULL;
620 k = sd_id128_from_string(*item, &id);
622 log_error("Failed to parse id128 '%s': %s",
623 *item, strerror(-k));
629 k = catalog_get(id, &msg);
631 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
632 "Failed to retrieve catalog entry for '%s': %s",
633 *item, strerror(-k));
639 dump_catalog_entry(f, id, msg, oneline);