]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/catalog.c
hashmap: introduce hash_ops to make struct Hashmap smaller
[thirdparty/systemd.git] / src / journal / catalog.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7
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.
12
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.
17
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/>.
20 ***/
21
22 #include <fcntl.h>
23 #include <stdio.h>
24 #include <unistd.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <sys/mman.h>
28 #include <locale.h>
29 #include <libgen.h>
30
31 #include "util.h"
32 #include "log.h"
33 #include "sparse-endian.h"
34 #include "sd-id128.h"
35 #include "hashmap.h"
36 #include "strv.h"
37 #include "strbuf.h"
38 #include "strxcpyx.h"
39 #include "conf-files.h"
40 #include "mkdir.h"
41 #include "catalog.h"
42 #include "siphash24.h"
43
44 const char * const catalog_file_dirs[] = {
45 "/usr/local/lib/systemd/catalog/",
46 "/usr/lib/systemd/catalog/",
47 NULL
48 };
49
50 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
51
52 typedef struct CatalogHeader {
53 uint8_t signature[8]; /* "RHHHKSLP" */
54 le32_t compatible_flags;
55 le32_t incompatible_flags;
56 le64_t header_size;
57 le64_t n_items;
58 le64_t catalog_item_size;
59 } CatalogHeader;
60
61 typedef struct CatalogItem {
62 sd_id128_t id;
63 char language[32];
64 le64_t offset;
65 } CatalogItem;
66
67 static unsigned long catalog_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) {
68 const CatalogItem *i = p;
69 uint64_t u;
70 size_t l, sz;
71 void *v;
72
73 l = strlen(i->language);
74 sz = sizeof(i->id) + l;
75 v = alloca(sz);
76
77 memcpy(mempcpy(v, &i->id, sizeof(i->id)), i->language, l);
78
79 siphash24((uint8_t*) &u, v, sz, hash_key);
80
81 return (unsigned long) u;
82 }
83
84 static int catalog_compare_func(const void *a, const void *b) {
85 const CatalogItem *i = a, *j = b;
86 unsigned k;
87
88 for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) {
89 if (i->id.bytes[k] < j->id.bytes[k])
90 return -1;
91 if (i->id.bytes[k] > j->id.bytes[k])
92 return 1;
93 }
94
95 return strcmp(i->language, j->language);
96 }
97
98 const struct hash_ops catalog_hash_ops = {
99 .hash = catalog_hash_func,
100 .compare = catalog_compare_func
101 };
102
103 static int finish_item(
104 Hashmap *h,
105 struct strbuf *sb,
106 sd_id128_t id,
107 const char *language,
108 const char *payload) {
109
110 ssize_t offset;
111 _cleanup_free_ CatalogItem *i = NULL;
112 int r;
113
114 assert(h);
115 assert(sb);
116 assert(payload);
117
118 offset = strbuf_add_string(sb, payload, strlen(payload));
119 if (offset < 0)
120 return log_oom();
121
122 i = new0(CatalogItem, 1);
123 if (!i)
124 return log_oom();
125
126 i->id = id;
127 if (language) {
128 assert(strlen(language) > 1 && strlen(language) < 32);
129 strcpy(i->language, language);
130 }
131 i->offset = htole64((uint64_t) offset);
132
133 r = hashmap_put(h, i, i);
134 if (r == -EEXIST) {
135 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.",
136 SD_ID128_FORMAT_VAL(id), language ? language : "C");
137 return 0;
138 } else if (r < 0)
139 return r;
140
141 i = NULL;
142 return 0;
143 }
144
145 int catalog_file_lang(const char* filename, char **lang) {
146 char *beg, *end, *_lang;
147
148 end = endswith(filename, ".catalog");
149 if (!end)
150 return 0;
151
152 beg = end - 1;
153 while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32)
154 beg --;
155
156 if (*beg != '.' || end <= beg + 1)
157 return 0;
158
159 _lang = strndup(beg + 1, end - beg - 1);
160 if (!_lang)
161 return -ENOMEM;
162
163 *lang = _lang;
164 return 1;
165 }
166
167 static int catalog_entry_lang(const char* filename, int line,
168 const char* t, const char* deflang, char **lang) {
169 size_t c;
170
171 c = strlen(t);
172 if (c == 0) {
173 log_error("[%s:%u] Language too short.", filename, line);
174 return -EINVAL;
175 }
176 if (c > 31) {
177 log_error("[%s:%u] language too long.", filename, line);
178 return -EINVAL;
179 }
180
181 if (deflang) {
182 if (streq(t, deflang)) {
183 log_warning("[%s:%u] language specified unnecessarily",
184 filename, line);
185 return 0;
186 } else
187 log_warning("[%s:%u] language differs from default for file",
188 filename, line);
189 }
190
191 *lang = strdup(t);
192 if (!*lang)
193 return -ENOMEM;
194
195 return 0;
196 }
197
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;
201 unsigned n = 0;
202 sd_id128_t id;
203 _cleanup_free_ char *deflang = NULL, *lang = NULL;
204 bool got_id = false, empty_line = true;
205 int r;
206
207 assert(h);
208 assert(sb);
209 assert(path);
210
211 f = fopen(path, "re");
212 if (!f) {
213 log_error("Failed to open file %s: %m", path);
214 return -errno;
215 }
216
217 r = catalog_file_lang(path, &deflang);
218 if (r < 0)
219 log_error("Failed to determine language for file %s: %m", path);
220 if (r == 1)
221 log_debug("File %s has language %s.", path, deflang);
222
223 for (;;) {
224 char line[LINE_MAX];
225 size_t a, b, c;
226 char *t;
227
228 if (!fgets(line, sizeof(line), f)) {
229 if (feof(f))
230 break;
231
232 log_error("Failed to read file %s: %m", path);
233 return -errno;
234 }
235
236 n++;
237
238 truncate_nl(line);
239
240 if (line[0] == 0) {
241 empty_line = true;
242 continue;
243 }
244
245 if (strchr(COMMENTS "\n", line[0]))
246 continue;
247
248 if (empty_line &&
249 strlen(line) >= 2+1+32 &&
250 line[0] == '-' &&
251 line[1] == '-' &&
252 line[2] == ' ' &&
253 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
254
255 bool with_language;
256 sd_id128_t jd;
257
258 /* New entry */
259
260 with_language = line[2+1+32] != '\0';
261 line[2+1+32] = '\0';
262
263 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
264
265 if (got_id) {
266 r = finish_item(h, sb, id, lang ?: deflang, payload);
267 if (r < 0)
268 return r;
269
270 free(lang);
271 lang = NULL;
272 }
273
274 if (with_language) {
275 t = strstrip(line + 2 + 1 + 32 + 1);
276
277 r = catalog_entry_lang(path, n, t, deflang, &lang);
278 if (r < 0)
279 return r;
280 }
281
282 got_id = true;
283 empty_line = false;
284 id = jd;
285
286 if (payload)
287 payload[0] = '\0';
288
289 continue;
290 }
291 }
292
293 /* Payload */
294 if (!got_id) {
295 log_error("[%s:%u] Got payload before ID.", path, n);
296 return -EINVAL;
297 }
298
299 a = payload ? strlen(payload) : 0;
300 b = strlen(line);
301
302 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
303 t = realloc(payload, c);
304 if (!t)
305 return log_oom();
306
307 if (empty_line) {
308 t[a] = '\n';
309 memcpy(t + a + 1, line, b);
310 t[a+b+1] = '\n';
311 t[a+b+2] = 0;
312 } else {
313 memcpy(t + a, line, b);
314 t[a+b] = '\n';
315 t[a+b+1] = 0;
316 }
317
318 payload = t;
319 empty_line = false;
320 }
321
322 if (got_id) {
323 r = finish_item(h, sb, id, lang ?: deflang, payload);
324 if (r < 0)
325 return r;
326 }
327
328 return 0;
329 }
330
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;
335 int r;
336 _cleanup_free_ char *d, *p = NULL;
337 size_t k;
338
339 d = dirname_malloc(database);
340 if (!d)
341 return log_oom();
342
343 r = mkdir_p(d, 0775);
344 if (r < 0) {
345 log_error("Recursive mkdir %s: %s", d, strerror(-r));
346 return r;
347 }
348
349 r = fopen_temporary(database, &w, &p);
350 if (r < 0) {
351 log_error("Failed to open database for writing: %s: %s",
352 database, strerror(-r));
353 return r;
354 }
355
356 zero(header);
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));
361
362 r = -EIO;
363
364 k = fwrite(&header, 1, sizeof(header), w);
365 if (k != sizeof(header)) {
366 log_error("%s: failed to write header.", p);
367 goto error;
368 }
369
370 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
371 if (k != n * sizeof(CatalogItem)) {
372 log_error("%s: failed to write database.", p);
373 goto error;
374 }
375
376 k = fwrite(sb->buf, 1, sb->len, w);
377 if (k != sb->len) {
378 log_error("%s: failed to write strings.", p);
379 goto error;
380 }
381
382 fflush(w);
383
384 if (ferror(w)) {
385 log_error("%s: failed to write database.", p);
386 goto error;
387 }
388
389 fchmod(fileno(w), 0644);
390
391 if (rename(p, database) < 0) {
392 log_error("rename (%s -> %s) failed: %m", p, database);
393 r = -errno;
394 goto error;
395 }
396
397 return ftell(w);
398
399 error:
400 unlink(p);
401 return r;
402 }
403
404 int catalog_update(const char* database, const char* root, const char* const* dirs) {
405 _cleanup_strv_free_ char **files = NULL;
406 char **f;
407 struct strbuf *sb = NULL;
408 _cleanup_hashmap_free_free_ Hashmap *h = NULL;
409 _cleanup_free_ CatalogItem *items = NULL;
410 CatalogItem *i;
411 Iterator j;
412 unsigned n;
413 long r;
414
415 h = hashmap_new(&catalog_hash_ops);
416 sb = strbuf_new();
417
418 if (!h || !sb) {
419 r = log_oom();
420 goto finish;
421 }
422
423 r = conf_files_list_strv(&files, ".catalog", root, dirs);
424 if (r < 0) {
425 log_error("Failed to get catalog files: %s", strerror(-r));
426 goto finish;
427 }
428
429 STRV_FOREACH(f, files) {
430 log_debug("Reading file '%s'", *f);
431 r = catalog_import_file(h, sb, *f);
432 if (r < 0) {
433 log_error("Failed to import file '%s': %s.",
434 *f, strerror(-r));
435 goto finish;
436 }
437 }
438
439 if (hashmap_size(h) <= 0) {
440 log_info("No items in catalog.");
441 goto finish;
442 } else
443 log_debug("Found %u items in catalog.", hashmap_size(h));
444
445 strbuf_complete(sb);
446
447 items = new(CatalogItem, hashmap_size(h));
448 if (!items) {
449 r = log_oom();
450 goto finish;
451 }
452
453 n = 0;
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);
458 items[n++] = *i;
459 }
460
461 assert(n == hashmap_size(h));
462 qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func);
463
464 r = write_catalog(database, h, sb, items, n);
465 if (r < 0)
466 log_error("Failed to write %s: %s", database, strerror(-r));
467 else
468 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
469 database, n, sb->len, r);
470
471 finish:
472 if (sb)
473 strbuf_cleanup(sb);
474
475 return r < 0 ? r : 0;
476 }
477
478 static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
479 const CatalogHeader *h;
480 int fd;
481 void *p;
482 struct stat st;
483
484 assert(_fd);
485 assert(_st);
486 assert(_p);
487
488 fd = open(database, O_RDONLY|O_CLOEXEC);
489 if (fd < 0)
490 return -errno;
491
492 if (fstat(fd, &st) < 0) {
493 safe_close(fd);
494 return -errno;
495 }
496
497 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
498 safe_close(fd);
499 return -EINVAL;
500 }
501
502 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
503 if (p == MAP_FAILED) {
504 safe_close(fd);
505 return -errno;
506 }
507
508 h = p;
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))) {
515 safe_close(fd);
516 munmap(p, st.st_size);
517 return -EBADMSG;
518 }
519
520 *_fd = fd;
521 *_st = st;
522 *_p = p;
523
524 return 0;
525 }
526
527 static const char *find_id(void *p, sd_id128_t id) {
528 CatalogItem key, *f = NULL;
529 const CatalogHeader *h = p;
530 const char *loc;
531
532 zero(key);
533 key.id = id;
534
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;
539
540 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
541 if (!f) {
542 char *e;
543
544 e = strchr(key.language, '_');
545 if (e) {
546 *e = 0;
547 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
548 }
549 }
550 }
551
552 if (!f) {
553 zero(key.language);
554 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
555 }
556
557 if (!f)
558 return NULL;
559
560 return (const char*) p +
561 le64toh(h->header_size) +
562 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
563 le64toh(f->offset);
564 }
565
566 int catalog_get(const char* database, sd_id128_t id, char **_text) {
567 _cleanup_close_ int fd = -1;
568 void *p = NULL;
569 struct stat st;
570 char *text = NULL;
571 int r;
572 const char *s;
573
574 assert(_text);
575
576 r = open_mmap(database, &fd, &st, &p);
577 if (r < 0)
578 return r;
579
580 s = find_id(p, id);
581 if (!s) {
582 r = -ENOENT;
583 goto finish;
584 }
585
586 text = strdup(s);
587 if (!text) {
588 r = -ENOMEM;
589 goto finish;
590 }
591
592 *_text = text;
593 r = 0;
594
595 finish:
596 if (p)
597 munmap(p, st.st_size);
598
599 return r;
600 }
601
602 static char *find_header(const char *s, const char *header) {
603
604 for (;;) {
605 const char *v, *e;
606
607 v = startswith(s, header);
608 if (v) {
609 v += strspn(v, WHITESPACE);
610 return strndup(v, strcspn(v, NEWLINE));
611 }
612
613 /* End of text */
614 e = strchr(s, '\n');
615 if (!e)
616 return NULL;
617
618 /* End of header */
619 if (e == s)
620 return NULL;
621
622 s = e + 1;
623 }
624 }
625
626 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
627 if (oneline) {
628 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
629
630 subject = find_header(s, "Subject:");
631 defined_by = find_header(s, "Defined-By:");
632
633 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
634 SD_ID128_FORMAT_VAL(id),
635 strna(defined_by), strna(subject));
636 } else
637 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
638 SD_ID128_FORMAT_VAL(id), s);
639 }
640
641
642 int catalog_list(FILE *f, const char *database, bool oneline) {
643 _cleanup_close_ int fd = -1;
644 void *p = NULL;
645 struct stat st;
646 const CatalogHeader *h;
647 const CatalogItem *items;
648 int r;
649 unsigned n;
650 sd_id128_t last_id;
651 bool last_id_set = false;
652
653 r = open_mmap(database, &fd, &st, &p);
654 if (r < 0)
655 return r;
656
657 h = p;
658 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
659
660 for (n = 0; n < le64toh(h->n_items); n++) {
661 const char *s;
662
663 if (last_id_set && sd_id128_equal(last_id, items[n].id))
664 continue;
665
666 assert_se(s = find_id(p, items[n].id));
667
668 dump_catalog_entry(f, items[n].id, s, oneline);
669
670 last_id_set = true;
671 last_id = items[n].id;
672 }
673
674 munmap(p, st.st_size);
675
676 return 0;
677 }
678
679 int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
680 char **item;
681 int r = 0;
682
683 STRV_FOREACH(item, items) {
684 sd_id128_t id;
685 int k;
686 _cleanup_free_ char *msg = NULL;
687
688 k = sd_id128_from_string(*item, &id);
689 if (k < 0) {
690 log_error("Failed to parse id128 '%s': %s",
691 *item, strerror(-k));
692 if (r == 0)
693 r = k;
694 continue;
695 }
696
697 k = catalog_get(database, id, &msg);
698 if (k < 0) {
699 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
700 "Failed to retrieve catalog entry for '%s': %s",
701 *item, strerror(-k));
702 if (r == 0)
703 r = k;
704 continue;
705 }
706
707 dump_catalog_entry(f, id, msg, oneline);
708 }
709
710 return r;
711 }