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 int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
163 _cleanup_fclose_ FILE *f = NULL;
164 _cleanup_free_ char *payload = NULL;
167 _cleanup_free_ char *deflang = NULL, *lang = NULL;
168 bool got_id = false, empty_line = true;
175 f = fopen(path, "re");
177 log_error("Failed to open file %s: %m", path);
181 r = catalog_file_lang(path, &deflang);
183 log_error("Failed to determine language for file %s: %m", path);
185 log_debug("File %s has language %s.", path, deflang);
192 if (!fgets(line, sizeof(line), f)) {
196 log_error("Failed to read file %s: %m", path);
209 if (strchr(COMMENTS "\n", line[0]))
213 strlen(line) >= 2+1+32 &&
217 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
224 with_language = line[2+1+32] != '\0';
227 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
230 r = finish_item(h, sb, id, lang ?: deflang, payload);
239 t = strstrip(line + 2 + 1 + 32 + 1);
243 log_error("[%s:%u] Language too short.", path, n);
247 log_error("[%s:%u] language too long.", path, n);
252 log_warning("[%s:%u] language %s", path, n,
254 "specified unnecessarily" :
255 "differs from default for file");
275 log_error("[%s:%u] Got payload before ID.", path, n);
279 a = payload ? strlen(payload) : 0;
282 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
283 t = realloc(payload, c);
289 memcpy(t + a + 1, line, b);
293 memcpy(t + a, line, b);
303 r = finish_item(h, sb, id, lang ?: deflang, payload);
311 static long write_catalog(const char *database, Hashmap *h, struct strbuf *sb,
312 CatalogItem *items, size_t n) {
313 CatalogHeader header;
314 _cleanup_fclose_ FILE *w = NULL;
316 _cleanup_free_ char *d, *p = NULL;
319 d = dirname_malloc(database);
323 r = mkdir_p(d, 0775);
325 log_error("Recursive mkdir %s: %s", d, strerror(-r));
329 r = fopen_temporary(database, &w, &p);
331 log_error("Failed to open database for writing: %s: %s",
332 database, strerror(-r));
337 memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
338 header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
339 header.catalog_item_size = htole64(sizeof(CatalogItem));
340 header.n_items = htole64(hashmap_size(h));
344 k = fwrite(&header, 1, sizeof(header), w);
345 if (k != sizeof(header)) {
346 log_error("%s: failed to write header.", p);
350 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
351 if (k != n * sizeof(CatalogItem)) {
352 log_error("%s: failed to write database.", p);
356 k = fwrite(sb->buf, 1, sb->len, w);
358 log_error("%s: failed to write strings.", p);
365 log_error("%s: failed to write database.", p);
369 fchmod(fileno(w), 0644);
371 if (rename(p, database) < 0) {
372 log_error("rename (%s -> %s) failed: %m", p, database);
384 int catalog_update(const char* database, const char* root, const char* const* dirs) {
385 _cleanup_strv_free_ char **files = NULL;
387 struct strbuf *sb = NULL;
388 _cleanup_hashmap_free_free_ Hashmap *h = NULL;
389 _cleanup_free_ CatalogItem *items = NULL;
395 h = hashmap_new(catalog_hash_func, catalog_compare_func);
403 r = conf_files_list_strv(&files, ".catalog", root, dirs);
405 log_error("Failed to get catalog files: %s", strerror(-r));
409 STRV_FOREACH(f, files) {
410 log_debug("Reading file '%s'", *f);
411 r = catalog_import_file(h, sb, *f);
413 log_error("Failed to import file '%s': %s.",
419 if (hashmap_size(h) <= 0) {
420 log_info("No items in catalog.");
423 log_debug("Found %u items in catalog.", hashmap_size(h));
427 items = new(CatalogItem, hashmap_size(h));
434 HASHMAP_FOREACH(i, h, j) {
435 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
436 SD_ID128_FORMAT_VAL(i->id),
437 isempty(i->language) ? "C" : i->language);
441 assert(n == hashmap_size(h));
442 qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func);
444 r = write_catalog(database, h, sb, items, n);
446 log_error("Failed to write %s: %s", database, strerror(-r));
448 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
449 database, n, sb->len, r);
455 return r < 0 ? r : 0;
458 static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
459 const CatalogHeader *h;
468 fd = open(database, O_RDONLY|O_CLOEXEC);
472 if (fstat(fd, &st) < 0) {
477 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
482 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
483 if (p == MAP_FAILED) {
489 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
490 le64toh(h->header_size) < sizeof(CatalogHeader) ||
491 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
492 h->incompatible_flags != 0 ||
493 le64toh(h->n_items) <= 0 ||
494 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
496 munmap(p, st.st_size);
507 static const char *find_id(void *p, sd_id128_t id) {
508 CatalogItem key, *f = NULL;
509 const CatalogHeader *h = p;
515 loc = setlocale(LC_MESSAGES, NULL);
516 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
517 strncpy(key.language, loc, sizeof(key.language));
518 key.language[strcspn(key.language, ".@")] = 0;
520 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
524 e = strchr(key.language, '_');
527 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
534 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
540 return (const char*) p +
541 le64toh(h->header_size) +
542 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
546 int catalog_get(const char* database, sd_id128_t id, char **_text) {
547 _cleanup_close_ int fd = -1;
556 r = open_mmap(database, &fd, &st, &p);
577 munmap(p, st.st_size);
582 static char *find_header(const char *s, const char *header) {
587 v = startswith(s, header);
589 v += strspn(v, WHITESPACE);
590 return strndup(v, strcspn(v, NEWLINE));
606 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
608 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
610 subject = find_header(s, "Subject:");
611 defined_by = find_header(s, "Defined-By:");
613 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
614 SD_ID128_FORMAT_VAL(id),
615 strna(defined_by), strna(subject));
617 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
618 SD_ID128_FORMAT_VAL(id), s);
622 int catalog_list(FILE *f, const char *database, bool oneline) {
623 _cleanup_close_ int fd = -1;
626 const CatalogHeader *h;
627 const CatalogItem *items;
631 bool last_id_set = false;
633 r = open_mmap(database, &fd, &st, &p);
638 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
640 for (n = 0; n < le64toh(h->n_items); n++) {
643 if (last_id_set && sd_id128_equal(last_id, items[n].id))
646 assert_se(s = find_id(p, items[n].id));
648 dump_catalog_entry(f, items[n].id, s, oneline);
651 last_id = items[n].id;
654 munmap(p, st.st_size);
659 int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
663 STRV_FOREACH(item, items) {
666 _cleanup_free_ char *msg = NULL;
668 k = sd_id128_from_string(*item, &id);
670 log_error("Failed to parse id128 '%s': %s",
671 *item, strerror(-k));
677 k = catalog_get(database, id, &msg);
679 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
680 "Failed to retrieve catalog entry for '%s': %s",
681 *item, strerror(-k));
687 dump_catalog_entry(f, id, msg, oneline);