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) {
106 _cleanup_free_ CatalogItem *i = NULL;
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");
140 int catalog_file_lang(const char* filename, char **lang) {
141 char *beg, *end, *_lang;
143 end = endswith(filename, ".catalog");
148 while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32)
151 if (*beg != '.' || end <= beg + 1)
154 _lang = strndup(beg + 1, end - beg - 1);
162 static int catalog_entry_lang(const char* filename, int line,
163 const char* t, const char* deflang, char **lang) {
168 log_error("[%s:%u] Language too short.", filename, line);
172 log_error("[%s:%u] language too long.", filename, line);
177 if (streq(t, deflang)) {
178 log_warning("[%s:%u] language specified unnecessarily",
182 log_warning("[%s:%u] language differs from default for file",
193 int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
194 _cleanup_fclose_ FILE *f = NULL;
195 _cleanup_free_ char *payload = NULL;
198 _cleanup_free_ char *deflang = NULL, *lang = NULL;
199 bool got_id = false, empty_line = true;
206 f = fopen(path, "re");
208 log_error("Failed to open file %s: %m", path);
212 r = catalog_file_lang(path, &deflang);
214 log_error("Failed to determine language for file %s: %m", path);
216 log_debug("File %s has language %s.", path, deflang);
223 if (!fgets(line, sizeof(line), f)) {
227 log_error("Failed to read file %s: %m", path);
240 if (strchr(COMMENTS "\n", line[0]))
244 strlen(line) >= 2+1+32 &&
248 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
255 with_language = line[2+1+32] != '\0';
258 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
261 r = finish_item(h, sb, id, lang ?: deflang, payload);
270 t = strstrip(line + 2 + 1 + 32 + 1);
272 r = catalog_entry_lang(path, n, t, deflang, &lang);
290 log_error("[%s:%u] Got payload before ID.", path, n);
294 a = payload ? strlen(payload) : 0;
297 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
298 t = realloc(payload, c);
304 memcpy(t + a + 1, line, b);
308 memcpy(t + a, line, b);
318 r = finish_item(h, sb, id, lang ?: deflang, payload);
326 static long write_catalog(const char *database, Hashmap *h, struct strbuf *sb,
327 CatalogItem *items, size_t n) {
328 CatalogHeader header;
329 _cleanup_fclose_ FILE *w = NULL;
331 _cleanup_free_ char *d, *p = NULL;
334 d = dirname_malloc(database);
338 r = mkdir_p(d, 0775);
340 log_error("Recursive mkdir %s: %s", d, strerror(-r));
344 r = fopen_temporary(database, &w, &p);
346 log_error("Failed to open database for writing: %s: %s",
347 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));
359 k = fwrite(&header, 1, sizeof(header), w);
360 if (k != sizeof(header)) {
361 log_error("%s: failed to write header.", p);
365 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
366 if (k != n * sizeof(CatalogItem)) {
367 log_error("%s: failed to write database.", p);
371 k = fwrite(sb->buf, 1, sb->len, w);
373 log_error("%s: failed to write strings.", p);
380 log_error("%s: failed to write database.", p);
384 fchmod(fileno(w), 0644);
386 if (rename(p, database) < 0) {
387 log_error("rename (%s -> %s) failed: %m", p, database);
399 int catalog_update(const char* database, const char* root, const char* const* dirs) {
400 _cleanup_strv_free_ char **files = NULL;
402 struct strbuf *sb = NULL;
403 _cleanup_hashmap_free_free_ Hashmap *h = NULL;
404 _cleanup_free_ CatalogItem *items = NULL;
410 h = hashmap_new(catalog_hash_func, catalog_compare_func);
418 r = conf_files_list_strv(&files, ".catalog", root, dirs);
420 log_error("Failed to get catalog files: %s", strerror(-r));
424 STRV_FOREACH(f, files) {
425 log_debug("Reading file '%s'", *f);
426 r = catalog_import_file(h, sb, *f);
428 log_error("Failed to import file '%s': %s.",
434 if (hashmap_size(h) <= 0) {
435 log_info("No items in catalog.");
438 log_debug("Found %u items in catalog.", hashmap_size(h));
442 items = new(CatalogItem, hashmap_size(h));
449 HASHMAP_FOREACH(i, h, j) {
450 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
451 SD_ID128_FORMAT_VAL(i->id),
452 isempty(i->language) ? "C" : i->language);
456 assert(n == hashmap_size(h));
457 qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func);
459 r = write_catalog(database, h, sb, items, n);
461 log_error("Failed to write %s: %s", database, strerror(-r));
463 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
464 database, n, sb->len, r);
470 return r < 0 ? r : 0;
473 static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
474 const CatalogHeader *h;
483 fd = open(database, O_RDONLY|O_CLOEXEC);
487 if (fstat(fd, &st) < 0) {
492 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
497 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
498 if (p == MAP_FAILED) {
504 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
505 le64toh(h->header_size) < sizeof(CatalogHeader) ||
506 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
507 h->incompatible_flags != 0 ||
508 le64toh(h->n_items) <= 0 ||
509 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
511 munmap(p, st.st_size);
522 static const char *find_id(void *p, sd_id128_t id) {
523 CatalogItem key, *f = NULL;
524 const CatalogHeader *h = p;
530 loc = setlocale(LC_MESSAGES, NULL);
531 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
532 strncpy(key.language, loc, sizeof(key.language));
533 key.language[strcspn(key.language, ".@")] = 0;
535 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
539 e = strchr(key.language, '_');
542 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
549 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
555 return (const char*) p +
556 le64toh(h->header_size) +
557 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
561 int catalog_get(const char* database, sd_id128_t id, char **_text) {
562 _cleanup_close_ int fd = -1;
571 r = open_mmap(database, &fd, &st, &p);
592 munmap(p, st.st_size);
597 static char *find_header(const char *s, const char *header) {
602 v = startswith(s, header);
604 v += strspn(v, WHITESPACE);
605 return strndup(v, strcspn(v, NEWLINE));
621 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
623 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
625 subject = find_header(s, "Subject:");
626 defined_by = find_header(s, "Defined-By:");
628 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
629 SD_ID128_FORMAT_VAL(id),
630 strna(defined_by), strna(subject));
632 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
633 SD_ID128_FORMAT_VAL(id), s);
637 int catalog_list(FILE *f, const char *database, bool oneline) {
638 _cleanup_close_ int fd = -1;
641 const CatalogHeader *h;
642 const CatalogItem *items;
646 bool last_id_set = false;
648 r = open_mmap(database, &fd, &st, &p);
653 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
655 for (n = 0; n < le64toh(h->n_items); n++) {
658 if (last_id_set && sd_id128_equal(last_id, items[n].id))
661 assert_se(s = find_id(p, items[n].id));
663 dump_catalog_entry(f, items[n].id, s, oneline);
666 last_id = items[n].id;
669 munmap(p, st.st_size);
674 int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
678 STRV_FOREACH(item, items) {
681 _cleanup_free_ char *msg = NULL;
683 k = sd_id128_from_string(*item, &id);
685 log_error("Failed to parse id128 '%s': %s",
686 *item, strerror(-k));
692 k = catalog_get(database, id, &msg);
694 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
695 "Failed to retrieve catalog entry for '%s': %s",
696 *item, strerror(-k));
702 dump_catalog_entry(f, id, msg, oneline);