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"
42 #include "siphash24.h"
44 const char * const catalog_file_dirs[] = {
45 "/usr/local/lib/systemd/catalog/",
46 "/usr/lib/systemd/catalog/",
50 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
52 typedef struct CatalogHeader {
53 uint8_t signature[8]; /* "RHHHKSLP" */
54 le32_t compatible_flags;
55 le32_t incompatible_flags;
58 le64_t catalog_item_size;
61 typedef struct CatalogItem {
67 unsigned long catalog_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) {
68 const CatalogItem *i = p;
73 l = strlen(i->language);
74 sz = sizeof(i->id) + l;
77 memcpy(mempcpy(v, &i->id, sizeof(i->id)), i->language, l);
79 siphash24((uint8_t*) &u, v, sz, hash_key);
81 return (unsigned long) u;
84 int catalog_compare_func(const void *a, const void *b) {
85 const CatalogItem *i = a, *j = b;
88 for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) {
89 if (i->id.bytes[k] < j->id.bytes[k])
91 if (i->id.bytes[k] > j->id.bytes[k])
95 return strcmp(i->language, j->language);
98 static int finish_item(
102 const char *language,
103 const char *payload) {
113 offset = strbuf_add_string(sb, payload, strlen(payload));
117 i = new0(CatalogItem, 1);
123 assert(strlen(language) > 1 && strlen(language) < 32);
124 strcpy(i->language, language);
126 i->offset = htole64((uint64_t) offset);
128 r = hashmap_put(h, i, i);
130 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.",
131 SD_ID128_FORMAT_VAL(id), language ? language : "C");
139 int catalog_file_lang(const char* filename, char **lang) {
140 char *beg, *end, *_lang;
142 end = endswith(filename, ".catalog");
147 while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32)
150 if (*beg != '.' || end <= beg + 1)
153 _lang = strndup(beg + 1, end - beg - 1);
161 int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
162 _cleanup_fclose_ FILE *f = NULL;
163 _cleanup_free_ char *payload = NULL;
166 _cleanup_free_ char *deflang = NULL, *lang = NULL;
167 bool got_id = false, empty_line = true;
174 f = fopen(path, "re");
176 log_error("Failed to open file %s: %m", path);
180 r = catalog_file_lang(path, &deflang);
182 log_error("Failed to determine language for file %s: %m", path);
184 log_debug("File %s has language %s.", path, deflang);
191 if (!fgets(line, sizeof(line), f)) {
195 log_error("Failed to read file %s: %m", path);
208 if (strchr(COMMENTS "\n", line[0]))
212 strlen(line) >= 2+1+32 &&
216 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
223 with_language = line[2+1+32] != '\0';
226 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
229 r = finish_item(h, sb, id, lang ?: deflang, payload);
238 t = strstrip(line + 2 + 1 + 32 + 1);
242 log_error("[%s:%u] Language too short.", path, n);
246 log_error("[%s:%u] language too long.", path, n);
251 log_warning("[%s:%u] language %s", path, n,
253 "specified unnecessarily" :
254 "differs from default for file");
274 log_error("[%s:%u] Got payload before ID.", path, n);
278 a = payload ? strlen(payload) : 0;
281 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
282 t = realloc(payload, c);
288 memcpy(t + a + 1, line, b);
292 memcpy(t + a, line, b);
302 r = finish_item(h, sb, id, lang ?: deflang, payload);
310 static long write_catalog(const char *database, Hashmap *h, struct strbuf *sb,
311 CatalogItem *items, size_t n) {
312 CatalogHeader header;
313 _cleanup_fclose_ FILE *w = NULL;
315 _cleanup_free_ char *d, *p = NULL;
318 d = dirname_malloc(database);
322 r = mkdir_p(d, 0775);
324 log_error("Recursive mkdir %s: %s", d, strerror(-r));
328 r = fopen_temporary(database, &w, &p);
330 log_error("Failed to open database for writing: %s: %s",
331 database, strerror(-r));
336 memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
337 header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
338 header.catalog_item_size = htole64(sizeof(CatalogItem));
339 header.n_items = htole64(hashmap_size(h));
343 k = fwrite(&header, 1, sizeof(header), w);
344 if (k != sizeof(header)) {
345 log_error("%s: failed to write header.", p);
349 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
350 if (k != n * sizeof(CatalogItem)) {
351 log_error("%s: failed to write database.", p);
355 k = fwrite(sb->buf, 1, sb->len, w);
357 log_error("%s: failed to write strings.", p);
364 log_error("%s: failed to write database.", p);
368 fchmod(fileno(w), 0644);
370 if (rename(p, database) < 0) {
371 log_error("rename (%s -> %s) failed: %m", p, database);
383 int catalog_update(const char* database, const char* root, const char* const* dirs) {
384 _cleanup_strv_free_ char **files = NULL;
387 struct strbuf *sb = NULL;
388 _cleanup_free_ CatalogItem *items = NULL;
394 h = hashmap_new(catalog_hash_func, catalog_compare_func);
402 r = conf_files_list_strv(&files, ".catalog", root, dirs);
404 log_error("Failed to get catalog files: %s", strerror(-r));
408 STRV_FOREACH(f, files) {
409 log_debug("reading file '%s'", *f);
410 catalog_import_file(h, sb, *f);
413 if (hashmap_size(h) <= 0) {
414 log_info("No items in catalog.");
418 log_debug("Found %u items in catalog.", hashmap_size(h));
422 items = new(CatalogItem, hashmap_size(h));
429 HASHMAP_FOREACH(i, h, j) {
430 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
431 SD_ID128_FORMAT_VAL(i->id),
432 isempty(i->language) ? "C" : i->language);
436 assert(n == hashmap_size(h));
437 qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func);
439 r = write_catalog(database, h, sb, items, n);
441 log_error("Failed to write %s: %s", database, strerror(-r));
443 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
444 database, n, sb->len, r);
450 hashmap_free_free(h);
454 return r < 0 ? r : 0;
457 static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
458 const CatalogHeader *h;
467 fd = open(database, O_RDONLY|O_CLOEXEC);
471 if (fstat(fd, &st) < 0) {
476 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
481 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
482 if (p == MAP_FAILED) {
488 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
489 le64toh(h->header_size) < sizeof(CatalogHeader) ||
490 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
491 h->incompatible_flags != 0 ||
492 le64toh(h->n_items) <= 0 ||
493 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
495 munmap(p, st.st_size);
506 static const char *find_id(void *p, sd_id128_t id) {
507 CatalogItem key, *f = NULL;
508 const CatalogHeader *h = p;
514 loc = setlocale(LC_MESSAGES, NULL);
515 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
516 strncpy(key.language, loc, sizeof(key.language));
517 key.language[strcspn(key.language, ".@")] = 0;
519 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
523 e = strchr(key.language, '_');
526 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
533 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
539 return (const char*) p +
540 le64toh(h->header_size) +
541 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
545 int catalog_get(const char* database, sd_id128_t id, char **_text) {
546 _cleanup_close_ int fd = -1;
555 r = open_mmap(database, &fd, &st, &p);
576 munmap(p, st.st_size);
581 static char *find_header(const char *s, const char *header) {
586 v = startswith(s, header);
588 v += strspn(v, WHITESPACE);
589 return strndup(v, strcspn(v, NEWLINE));
605 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
607 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
609 subject = find_header(s, "Subject:");
610 defined_by = find_header(s, "Defined-By:");
612 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
613 SD_ID128_FORMAT_VAL(id),
614 strna(defined_by), strna(subject));
616 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
617 SD_ID128_FORMAT_VAL(id), s);
621 int catalog_list(FILE *f, const char *database, bool oneline) {
622 _cleanup_close_ int fd = -1;
625 const CatalogHeader *h;
626 const CatalogItem *items;
630 bool last_id_set = false;
632 r = open_mmap(database, &fd, &st, &p);
637 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
639 for (n = 0; n < le64toh(h->n_items); n++) {
642 if (last_id_set && sd_id128_equal(last_id, items[n].id))
645 assert_se(s = find_id(p, items[n].id));
647 dump_catalog_entry(f, items[n].id, s, oneline);
650 last_id = items[n].id;
653 munmap(p, st.st_size);
658 int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
662 STRV_FOREACH(item, items) {
665 _cleanup_free_ char *msg = NULL;
667 k = sd_id128_from_string(*item, &id);
669 log_error("Failed to parse id128 '%s': %s",
670 *item, strerror(-k));
676 k = catalog_get(database, id, &msg);
678 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
679 "Failed to retrieve catalog entry for '%s': %s",
680 *item, strerror(-k));
686 dump_catalog_entry(f, id, msg, oneline);