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