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