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