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