]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/catalog.c
journal: cleanup up error handling in update_catalog()
[thirdparty/systemd.git] / src / journal / catalog.c
CommitLineData
d4205751
LP
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>
844ec79b 29#include <libgen.h>
d4205751
LP
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"
18cd5fe9 38#include "strxcpyx.h"
d4205751
LP
39#include "conf-files.h"
40#include "mkdir.h"
41#include "catalog.h"
9bf3b535 42#include "siphash24.h"
d4205751 43
844ec79b 44const char * const catalog_file_dirs[] = {
d4205751
LP
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
52typedef struct CatalogHeader {
53 uint8_t signature[8]; /* "RHHHKSLP" */
54 le32_t compatible_flags;
55 le32_t incompatible_flags;
83f6936a
LP
56 le64_t header_size;
57 le64_t n_items;
58 le64_t catalog_item_size;
d4205751
LP
59} CatalogHeader;
60
61typedef struct CatalogItem {
62 sd_id128_t id;
63 char language[32];
83f6936a 64 le64_t offset;
d4205751
LP
65} CatalogItem;
66
9bf3b535 67unsigned long catalog_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) {
d4205751 68 const CatalogItem *i = p;
9bf3b535
LP
69 uint64_t u;
70 size_t l, sz;
71 void *v;
d4205751 72
9bf3b535
LP
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;
d4205751
LP
82}
83
844ec79b 84int catalog_compare_func(const void *a, const void *b) {
d4205751
LP
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
18cd5fe9 95 return strcmp(i->language, j->language);
d4205751
LP
96}
97
98static 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;
e3b9d9c8 106 _cleanup_free_ CatalogItem *i = NULL;
d4205751
LP
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
d4205751
LP
117 i = new0(CatalogItem, 1);
118 if (!i)
119 return log_oom();
120
121 i->id = id;
c7332b08
ZJS
122 if (language) {
123 assert(strlen(language) > 1 && strlen(language) < 32);
124 strcpy(i->language, language);
125 }
83f6936a 126 i->offset = htole64((uint64_t) offset);
d4205751
LP
127
128 r = hashmap_put(h, i, i);
e3b9d9c8 129 if (r == -EEXIST) {
18cd5fe9
ZJS
130 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.",
131 SD_ID128_FORMAT_VAL(id), language ? language : "C");
d4205751 132 return 0;
e3b9d9c8
ZJS
133 } else if (r < 0)
134 return r;
d4205751 135
e3b9d9c8 136 i = NULL;
d4205751
LP
137 return 0;
138}
139
c7332b08
ZJS
140int 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;
4b8268f8 148 while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32)
c7332b08
ZJS
149 beg --;
150
4b8268f8 151 if (*beg != '.' || end <= beg + 1)
c7332b08
ZJS
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
844ec79b 162int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
d4205751
LP
163 _cleanup_fclose_ FILE *f = NULL;
164 _cleanup_free_ char *payload = NULL;
165 unsigned n = 0;
166 sd_id128_t id;
c7332b08 167 _cleanup_free_ char *deflang = NULL, *lang = NULL;
d4205751
LP
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
c7332b08
ZJS
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
d4205751
LP
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
d3b6d0c2 209 if (strchr(COMMENTS "\n", line[0]))
d4205751
LP
210 continue;
211
212 if (empty_line &&
213 strlen(line) >= 2+1+32 &&
214 line[0] == '-' &&
215 line[1] == '-' &&
216 line[2] == ' ' &&
18cd5fe9 217 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
d4205751
LP
218
219 bool with_language;
220 sd_id128_t jd;
221
222 /* New entry */
223
18cd5fe9
ZJS
224 with_language = line[2+1+32] != '\0';
225 line[2+1+32] = '\0';
d4205751
LP
226
227 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
228
229 if (got_id) {
c7332b08 230 r = finish_item(h, sb, id, lang ?: deflang, payload);
d4205751
LP
231 if (r < 0)
232 return r;
c7332b08
ZJS
233
234 free(lang);
235 lang = NULL;
d4205751
LP
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 }
c7332b08 246 if (c > 31) {
d4205751
LP
247 log_error("[%s:%u] language too long.", path, n);
248 return -EINVAL;
249 }
250
c7332b08
ZJS
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 }
d4205751
LP
261
262 got_id = true;
263 empty_line = false;
264 id = jd;
265
266 if (payload)
18cd5fe9 267 payload[0] = '\0';
d4205751
LP
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) {
c7332b08 303 r = finish_item(h, sb, id, lang ?: deflang, payload);
d4205751
LP
304 if (r < 0)
305 return r;
306 }
307
308 return 0;
309}
310
844ec79b
ZJS
311static 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;
7fd1b19b 316 _cleanup_free_ char *d, *p = NULL;
844ec79b
ZJS
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));
80343dc1 341
844ec79b
ZJS
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
379error:
380 unlink(p);
381 return r;
382}
383
384int catalog_update(const char* database, const char* root, const char* const* dirs) {
d4205751 385 _cleanup_strv_free_ char **files = NULL;
d4205751 386 char **f;
d4205751 387 struct strbuf *sb = NULL;
e3b9d9c8 388 _cleanup_hashmap_free_free_ Hashmap *h = NULL;
d4205751
LP
389 _cleanup_free_ CatalogItem *items = NULL;
390 CatalogItem *i;
d4205751
LP
391 Iterator j;
392 unsigned n;
844ec79b 393 long r;
d4205751
LP
394
395 h = hashmap_new(catalog_hash_func, catalog_compare_func);
d4205751 396 sb = strbuf_new();
844ec79b
ZJS
397
398 if (!h || !sb) {
d4205751
LP
399 r = log_oom();
400 goto finish;
401 }
402
844ec79b 403 r = conf_files_list_strv(&files, ".catalog", root, dirs);
d4205751
LP
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) {
e3b9d9c8
ZJS
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 }
d4205751
LP
417 }
418
419 if (hashmap_size(h) <= 0) {
420 log_info("No items in catalog.");
d4205751 421 goto finish;
80343dc1
ZJS
422 } else
423 log_debug("Found %u items in catalog.", hashmap_size(h));
d4205751
LP
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) {
18cd5fe9
ZJS
435 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
436 SD_ID128_FORMAT_VAL(i->id),
437 isempty(i->language) ? "C" : i->language);
d4205751
LP
438 items[n++] = *i;
439 }
440
441 assert(n == hashmap_size(h));
7ff7394d 442 qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func);
d4205751 443
844ec79b
ZJS
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);
d4205751 450
d4205751 451finish:
d4205751
LP
452 if (sb)
453 strbuf_cleanup(sb);
454
844ec79b 455 return r < 0 ? r : 0;
d4205751
LP
456}
457
844ec79b 458static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
d4205751
LP
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
844ec79b 468 fd = open(database, O_RDONLY|O_CLOEXEC);
d4205751
LP
469 if (fd < 0)
470 return -errno;
471
472 if (fstat(fd, &st) < 0) {
03e334a1 473 safe_close(fd);
d4205751
LP
474 return -errno;
475 }
476
477 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
03e334a1 478 safe_close(fd);
d4205751
LP
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) {
03e334a1 484 safe_close(fd);
d4205751
LP
485 return -errno;
486 }
487
488 h = p;
489 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
83f6936a
LP
490 le64toh(h->header_size) < sizeof(CatalogHeader) ||
491 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
d4205751 492 h->incompatible_flags != 0 ||
83f6936a
LP
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))) {
03e334a1 495 safe_close(fd);
d4205751
LP
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
507static 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
83f6936a 520 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
d4205751
LP
521 if (!f) {
522 char *e;
523
524 e = strchr(key.language, '_');
525 if (e) {
526 *e = 0;
83f6936a 527 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
d4205751
LP
528 }
529 }
530 }
531
532 if (!f) {
533 zero(key.language);
83f6936a 534 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
d4205751
LP
535 }
536
537 if (!f)
538 return NULL;
539
540 return (const char*) p +
83f6936a
LP
541 le64toh(h->header_size) +
542 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
543 le64toh(f->offset);
d4205751
LP
544}
545
844ec79b 546int catalog_get(const char* database, sd_id128_t id, char **_text) {
d4205751
LP
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
844ec79b 556 r = open_mmap(database, &fd, &st, &p);
d4205751
LP
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
575finish:
576 if (p)
577 munmap(p, st.st_size);
578
579 return r;
580}
581
582static 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
54b7254c
ZJS
606static 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
844ec79b 622int catalog_list(FILE *f, const char *database, bool oneline) {
d4205751
LP
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
844ec79b 633 r = open_mmap(database, &fd, &st, &p);
d4205751
LP
634 if (r < 0)
635 return r;
636
637 h = p;
83f6936a 638 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
d4205751 639
83f6936a 640 for (n = 0; n < le64toh(h->n_items); n++) {
d4205751 641 const char *s;
d4205751
LP
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
54b7254c 648 dump_catalog_entry(f, items[n].id, s, oneline);
d4205751
LP
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}
54b7254c 658
844ec79b 659int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
54b7254c
ZJS
660 char **item;
661 int r = 0;
662
663 STRV_FOREACH(item, items) {
664 sd_id128_t id;
665 int k;
7fd1b19b 666 _cleanup_free_ char *msg = NULL;
54b7254c
ZJS
667
668 k = sd_id128_from_string(*item, &id);
669 if (k < 0) {
670 log_error("Failed to parse id128 '%s': %s",
2e8fb702 671 *item, strerror(-k));
464264ac 672 if (r == 0)
54b7254c
ZJS
673 r = k;
674 continue;
675 }
676
844ec79b 677 k = catalog_get(database, id, &msg);
54b7254c
ZJS
678 if (k < 0) {
679 log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
680 "Failed to retrieve catalog entry for '%s': %s",
2e8fb702 681 *item, strerror(-k));
464264ac 682 if (r == 0)
54b7254c
ZJS
683 r = k;
684 continue;
685 }
686
687 dump_catalog_entry(f, id, msg, oneline);
688 }
689
690 return r;
691}