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 static 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 static 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 const struct hash_ops catalog_hash_ops = {
99 .hash = catalog_hash_func,
100 .compare = catalog_compare_func
103 static int finish_item(
107 const char *language,
108 const char *payload) {
111 _cleanup_free_ CatalogItem *i = NULL;
118 offset = strbuf_add_string(sb, payload, strlen(payload));
122 i = new0(CatalogItem, 1);
128 assert(strlen(language) > 1 && strlen(language) < 32);
129 strcpy(i->language, language);
131 i->offset = htole64((uint64_t) offset);
133 r = hashmap_put(h, i, i);
135 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.",
136 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 static int catalog_entry_lang(const char* filename, int line,
168 const char* t, const char* deflang, char **lang) {
173 log_error("[%s:%u] Language too short.", filename, line);
177 log_error("[%s:%u] language too long.", filename, line);
182 if (streq(t, deflang)) {
183 log_warning("[%s:%u] language specified unnecessarily",
187 log_warning("[%s:%u] language differs from default for file",
198 int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
199 _cleanup_fclose_ FILE *f = NULL;
200 _cleanup_free_ char *payload = NULL;
203 _cleanup_free_ char *deflang = NULL, *lang = NULL;
204 bool got_id = false, empty_line = true;
211 f = fopen(path, "re");
213 log_error("Failed to open file %s: %m", path);
217 r = catalog_file_lang(path, &deflang);
219 log_error("Failed to determine language for file %s: %m", path);
221 log_debug("File %s has language %s.", path, deflang);
228 if (!fgets(line, sizeof(line), f)) {
232 log_error("Failed to read file %s: %m", path);
245 if (strchr(COMMENTS "\n", line[0]))
249 strlen(line) >= 2+1+32 &&
253 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
260 with_language = line[2+1+32] != '\0';
263 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
266 r = finish_item(h, sb, id, lang ?: deflang, payload);
275 t = strstrip(line + 2 + 1 + 32 + 1);
277 r = catalog_entry_lang(path, n, t, deflang, &lang);
295 log_error("[%s:%u] Got payload before ID.", path, n);
299 a = payload ? strlen(payload) : 0;
302 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
303 t = realloc(payload, c);
309 memcpy(t + a + 1, line, b);
313 memcpy(t + a, line, b);
323 r = finish_item(h, sb, id, lang ?: deflang, payload);
331 static long write_catalog(const char *database, Hashmap *h, struct strbuf *sb,
332 CatalogItem *items, size_t n) {
333 CatalogHeader header;
334 _cleanup_fclose_ FILE *w = NULL;
336 _cleanup_free_ char *d, *p = NULL;
339 d = dirname_malloc(database);
343 r = mkdir_p(d, 0775);
345 log_error("Recursive mkdir %s: %s", d, strerror(-r));
349 r = fopen_temporary(database, &w, &p);
351 log_error("Failed to open database for writing: %s: %s",
352 database, strerror(-r));
357 memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
358 header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
359 header.catalog_item_size = htole64(sizeof(CatalogItem));
360 header.n_items = htole64(hashmap_size(h));
364 k = fwrite(&header, 1, sizeof(header), w);
365 if (k != sizeof(header)) {
366 log_error("%s: failed to write header.", p);
370 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
371 if (k != n * sizeof(CatalogItem)) {
372 log_error("%s: failed to write database.", p);
376 k = fwrite(sb->buf, 1, sb->len, w);
378 log_error("%s: failed to write strings.", p);
385 log_error("%s: failed to write database.", p);
389 fchmod(fileno(w), 0644);
391 if (rename(p, database) < 0) {
392 log_error("rename (%s -> %s) failed: %m", p, database);
404 int catalog_update(const char* database, const char* root, const char* const* dirs) {
405 _cleanup_strv_free_ char **files = NULL;
407 struct strbuf *sb = NULL;
408 _cleanup_hashmap_free_free_ Hashmap *h = NULL;
409 _cleanup_free_ CatalogItem *items = NULL;
415 h = hashmap_new(&catalog_hash_ops);
423 r = conf_files_list_strv(&files, ".catalog", root, dirs);
425 log_error("Failed to get catalog files: %s", strerror(-r));
429 STRV_FOREACH(f, files) {
430 log_debug("Reading file '%s'", *f);
431 r = catalog_import_file(h, sb, *f);
433 log_error("Failed to import file '%s': %s.",
439 if (hashmap_size(h) <= 0) {
440 log_info("No items in catalog.");
443 log_debug("Found %u items in catalog.", hashmap_size(h));
447 items = new(CatalogItem, hashmap_size(h));
454 HASHMAP_FOREACH(i, h, j) {
455 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
456 SD_ID128_FORMAT_VAL(i->id),
457 isempty(i->language) ? "C" : i->language);
461 assert(n == hashmap_size(h));
462 qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func);
464 r = write_catalog(database, h, sb, items, n);
466 log_error("Failed to write %s: %s", database, strerror(-r));
468 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
469 database, n, sb->len, r);
475 return r < 0 ? r : 0;
478 static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
479 const CatalogHeader *h;
488 fd = open(database, O_RDONLY|O_CLOEXEC);
492 if (fstat(fd, &st) < 0) {
497 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
502 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
503 if (p == MAP_FAILED) {
509 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
510 le64toh(h->header_size) < sizeof(CatalogHeader) ||
511 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
512 h->incompatible_flags != 0 ||
513 le64toh(h->n_items) <= 0 ||
514 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
516 munmap(p, st.st_size);
527 static const char *find_id(void *p, sd_id128_t id) {
528 CatalogItem key, *f = NULL;
529 const CatalogHeader *h = p;
535 loc = setlocale(LC_MESSAGES, NULL);
536 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
537 strncpy(key.language, loc, sizeof(key.language));
538 key.language[strcspn(key.language, ".@")] = 0;
540 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
544 e = strchr(key.language, '_');
547 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
554 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
560 return (const char*) p +
561 le64toh(h->header_size) +
562 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
566 int catalog_get(const char* database, sd_id128_t id, char **_text) {
567 _cleanup_close_ int fd = -1;
576 r = open_mmap(database, &fd, &st, &p);
597 munmap(p, st.st_size);
602 static char *find_header(const char *s, const char *header) {
607 v = startswith(s, header);
609 v += strspn(v, WHITESPACE);
610 return strndup(v, strcspn(v, NEWLINE));
626 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
628 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
630 subject = find_header(s, "Subject:");
631 defined_by = find_header(s, "Defined-By:");
633 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
634 SD_ID128_FORMAT_VAL(id),
635 strna(defined_by), strna(subject));
637 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
638 SD_ID128_FORMAT_VAL(id), s);
642 int catalog_list(FILE *f, const char *database, bool oneline) {
643 _cleanup_close_ int fd = -1;
646 const CatalogHeader *h;
647 const CatalogItem *items;
651 bool last_id_set = false;
653 r = open_mmap(database, &fd, &st, &p);
658 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
660 for (n = 0; n < le64toh(h->n_items); n++) {
663 if (last_id_set && sd_id128_equal(last_id, items[n].id))
666 assert_se(s = find_id(p, items[n].id));
668 dump_catalog_entry(f, items[n].id, s, oneline);
671 last_id = items[n].id;
674 munmap(p, st.st_size);
679 int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
683 STRV_FOREACH(item, items) {
686 _cleanup_free_ char *msg = NULL;
688 k = sd_id128_from_string(*item, &id);
690 log_error("Failed to parse id128 '%s': %s",
691 *item, strerror(-k));
697 k = catalog_get(database, id, &msg);
699 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
700 "Failed to retrieve catalog entry for '%s': %s",
701 *item, strerror(-k));
707 dump_catalog_entry(f, id, msg, oneline);