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