]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/catalog.c
util-lib: split our string related calls from util.[ch] into its own file string...
[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
07630cea 22#include <errno.h>
d4205751 23#include <fcntl.h>
07630cea 24#include <locale.h>
d4205751 25#include <stdio.h>
d4205751
LP
26#include <string.h>
27#include <sys/mman.h>
07630cea 28#include <unistd.h>
d4205751 29
d4205751 30#include "sd-id128.h"
07630cea
LP
31
32#include "catalog.h"
d4205751 33#include "conf-files.h"
07630cea
LP
34#include "hashmap.h"
35#include "log.h"
d4205751 36#include "mkdir.h"
9bf3b535 37#include "siphash24.h"
07630cea
LP
38#include "sparse-endian.h"
39#include "strbuf.h"
40#include "string-util.h"
41#include "strv.h"
42#include "util.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
b826ab58 67static void catalog_hash_func(const void *p, struct siphash *state) {
d4205751
LP
68 const CatalogItem *i = p;
69
b826ab58
TG
70 siphash24_compress(&i->id, sizeof(i->id), state);
71 siphash24_compress(i->language, strlen(i->language), state);
d4205751
LP
72}
73
d5099efc 74static int catalog_compare_func(const void *a, const void *b) {
d4205751
LP
75 const CatalogItem *i = a, *j = b;
76 unsigned k;
77
78 for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) {
79 if (i->id.bytes[k] < j->id.bytes[k])
80 return -1;
81 if (i->id.bytes[k] > j->id.bytes[k])
82 return 1;
83 }
84
18cd5fe9 85 return strcmp(i->language, j->language);
d4205751
LP
86}
87
d5099efc
MS
88const struct hash_ops catalog_hash_ops = {
89 .hash = catalog_hash_func,
90 .compare = catalog_compare_func
91};
92
d4205751
LP
93static int finish_item(
94 Hashmap *h,
95 struct strbuf *sb,
96 sd_id128_t id,
97 const char *language,
98 const char *payload) {
99
100 ssize_t offset;
e3b9d9c8 101 _cleanup_free_ CatalogItem *i = NULL;
d4205751
LP
102 int r;
103
104 assert(h);
105 assert(sb);
106 assert(payload);
107
108 offset = strbuf_add_string(sb, payload, strlen(payload));
109 if (offset < 0)
110 return log_oom();
111
d4205751
LP
112 i = new0(CatalogItem, 1);
113 if (!i)
114 return log_oom();
115
116 i->id = id;
c7332b08
ZJS
117 if (language) {
118 assert(strlen(language) > 1 && strlen(language) < 32);
119 strcpy(i->language, language);
120 }
83f6936a 121 i->offset = htole64((uint64_t) offset);
d4205751
LP
122
123 r = hashmap_put(h, i, i);
e3b9d9c8 124 if (r == -EEXIST) {
18cd5fe9
ZJS
125 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.",
126 SD_ID128_FORMAT_VAL(id), language ? language : "C");
d4205751 127 return 0;
e3b9d9c8
ZJS
128 } else if (r < 0)
129 return r;
d4205751 130
e3b9d9c8 131 i = NULL;
d4205751
LP
132 return 0;
133}
134
c7332b08
ZJS
135int catalog_file_lang(const char* filename, char **lang) {
136 char *beg, *end, *_lang;
137
138 end = endswith(filename, ".catalog");
139 if (!end)
140 return 0;
141
142 beg = end - 1;
4b8268f8 143 while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32)
c7332b08
ZJS
144 beg --;
145
4b8268f8 146 if (*beg != '.' || end <= beg + 1)
c7332b08
ZJS
147 return 0;
148
149 _lang = strndup(beg + 1, end - beg - 1);
150 if (!_lang)
151 return -ENOMEM;
152
153 *lang = _lang;
154 return 1;
155}
156
baf167ee
ZJS
157static int catalog_entry_lang(const char* filename, int line,
158 const char* t, const char* deflang, char **lang) {
159 size_t c;
160
161 c = strlen(t);
162 if (c == 0) {
163 log_error("[%s:%u] Language too short.", filename, line);
164 return -EINVAL;
165 }
166 if (c > 31) {
167 log_error("[%s:%u] language too long.", filename, line);
168 return -EINVAL;
169 }
170
171 if (deflang) {
172 if (streq(t, deflang)) {
173 log_warning("[%s:%u] language specified unnecessarily",
174 filename, line);
175 return 0;
176 } else
177 log_warning("[%s:%u] language differs from default for file",
178 filename, line);
179 }
180
181 *lang = strdup(t);
182 if (!*lang)
183 return -ENOMEM;
184
185 return 0;
186}
187
844ec79b 188int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
d4205751
LP
189 _cleanup_fclose_ FILE *f = NULL;
190 _cleanup_free_ char *payload = NULL;
191 unsigned n = 0;
192 sd_id128_t id;
c7332b08 193 _cleanup_free_ char *deflang = NULL, *lang = NULL;
d4205751
LP
194 bool got_id = false, empty_line = true;
195 int r;
196
197 assert(h);
198 assert(sb);
199 assert(path);
200
201 f = fopen(path, "re");
4a62c710
MS
202 if (!f)
203 return log_error_errno(errno, "Failed to open file %s: %m", path);
d4205751 204
c7332b08
ZJS
205 r = catalog_file_lang(path, &deflang);
206 if (r < 0)
56f64d95 207 log_error_errno(errno, "Failed to determine language for file %s: %m", path);
c7332b08
ZJS
208 if (r == 1)
209 log_debug("File %s has language %s.", path, deflang);
210
d4205751
LP
211 for (;;) {
212 char line[LINE_MAX];
213 size_t a, b, c;
214 char *t;
215
216 if (!fgets(line, sizeof(line), f)) {
217 if (feof(f))
218 break;
219
56f64d95 220 log_error_errno(errno, "Failed to read file %s: %m", path);
d4205751
LP
221 return -errno;
222 }
223
224 n++;
225
226 truncate_nl(line);
227
228 if (line[0] == 0) {
229 empty_line = true;
230 continue;
231 }
232
d3b6d0c2 233 if (strchr(COMMENTS "\n", line[0]))
d4205751
LP
234 continue;
235
236 if (empty_line &&
237 strlen(line) >= 2+1+32 &&
238 line[0] == '-' &&
239 line[1] == '-' &&
240 line[2] == ' ' &&
18cd5fe9 241 (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
d4205751
LP
242
243 bool with_language;
244 sd_id128_t jd;
245
246 /* New entry */
247
18cd5fe9
ZJS
248 with_language = line[2+1+32] != '\0';
249 line[2+1+32] = '\0';
d4205751
LP
250
251 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
252
253 if (got_id) {
c7332b08 254 r = finish_item(h, sb, id, lang ?: deflang, payload);
d4205751
LP
255 if (r < 0)
256 return r;
c7332b08 257
97b11eed 258 lang = mfree(lang);
d4205751
LP
259 }
260
261 if (with_language) {
262 t = strstrip(line + 2 + 1 + 32 + 1);
263
baf167ee
ZJS
264 r = catalog_entry_lang(path, n, t, deflang, &lang);
265 if (r < 0)
266 return r;
c7332b08 267 }
d4205751
LP
268
269 got_id = true;
270 empty_line = false;
271 id = jd;
272
273 if (payload)
18cd5fe9 274 payload[0] = '\0';
d4205751
LP
275
276 continue;
277 }
278 }
279
280 /* Payload */
281 if (!got_id) {
282 log_error("[%s:%u] Got payload before ID.", path, n);
283 return -EINVAL;
284 }
285
286 a = payload ? strlen(payload) : 0;
287 b = strlen(line);
288
289 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
290 t = realloc(payload, c);
291 if (!t)
292 return log_oom();
293
294 if (empty_line) {
295 t[a] = '\n';
296 memcpy(t + a + 1, line, b);
297 t[a+b+1] = '\n';
298 t[a+b+2] = 0;
299 } else {
300 memcpy(t + a, line, b);
301 t[a+b] = '\n';
302 t[a+b+1] = 0;
303 }
304
305 payload = t;
306 empty_line = false;
307 }
308
309 if (got_id) {
c7332b08 310 r = finish_item(h, sb, id, lang ?: deflang, payload);
d4205751
LP
311 if (r < 0)
312 return r;
313 }
314
315 return 0;
316}
317
844ec79b
ZJS
318static long write_catalog(const char *database, Hashmap *h, struct strbuf *sb,
319 CatalogItem *items, size_t n) {
320 CatalogHeader header;
321 _cleanup_fclose_ FILE *w = NULL;
322 int r;
7fd1b19b 323 _cleanup_free_ char *d, *p = NULL;
844ec79b
ZJS
324 size_t k;
325
326 d = dirname_malloc(database);
327 if (!d)
328 return log_oom();
329
330 r = mkdir_p(d, 0775);
eb56eb9b
MS
331 if (r < 0)
332 return log_error_errno(r, "Recursive mkdir %s: %m", d);
844ec79b
ZJS
333
334 r = fopen_temporary(database, &w, &p);
eb56eb9b
MS
335 if (r < 0)
336 return log_error_errno(r, "Failed to open database for writing: %s: %m",
337 database);
844ec79b
ZJS
338
339 zero(header);
340 memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
341 header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
342 header.catalog_item_size = htole64(sizeof(CatalogItem));
343 header.n_items = htole64(hashmap_size(h));
80343dc1 344
844ec79b
ZJS
345 r = -EIO;
346
347 k = fwrite(&header, 1, sizeof(header), w);
348 if (k != sizeof(header)) {
349 log_error("%s: failed to write header.", p);
350 goto error;
351 }
352
353 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
354 if (k != n * sizeof(CatalogItem)) {
355 log_error("%s: failed to write database.", p);
356 goto error;
357 }
358
359 k = fwrite(sb->buf, 1, sb->len, w);
360 if (k != sb->len) {
361 log_error("%s: failed to write strings.", p);
362 goto error;
363 }
364
dacd6cee
LP
365 r = fflush_and_check(w);
366 if (r < 0) {
367 log_error_errno(r, "%s: failed to write database: %m", p);
844ec79b
ZJS
368 goto error;
369 }
370
371 fchmod(fileno(w), 0644);
372
373 if (rename(p, database) < 0) {
dacd6cee 374 r = log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database);
844ec79b
ZJS
375 goto error;
376 }
377
378 return ftell(w);
379
380error:
dacd6cee 381 (void) unlink(p);
844ec79b
ZJS
382 return r;
383}
384
385int catalog_update(const char* database, const char* root, const char* const* dirs) {
d4205751 386 _cleanup_strv_free_ char **files = NULL;
d4205751 387 char **f;
d4205751 388 struct strbuf *sb = NULL;
e3b9d9c8 389 _cleanup_hashmap_free_free_ Hashmap *h = NULL;
d4205751
LP
390 _cleanup_free_ CatalogItem *items = NULL;
391 CatalogItem *i;
d4205751
LP
392 Iterator j;
393 unsigned n;
844ec79b 394 long r;
d4205751 395
d5099efc 396 h = hashmap_new(&catalog_hash_ops);
d4205751 397 sb = strbuf_new();
844ec79b
ZJS
398
399 if (!h || !sb) {
d4205751
LP
400 r = log_oom();
401 goto finish;
402 }
403
844ec79b 404 r = conf_files_list_strv(&files, ".catalog", root, dirs);
d4205751 405 if (r < 0) {
da927ba9 406 log_error_errno(r, "Failed to get catalog files: %m");
d4205751
LP
407 goto finish;
408 }
409
410 STRV_FOREACH(f, files) {
e3b9d9c8
ZJS
411 log_debug("Reading file '%s'", *f);
412 r = catalog_import_file(h, sb, *f);
413 if (r < 0) {
e53fc357 414 log_error_errno(r, "Failed to import file '%s': %m", *f);
e3b9d9c8
ZJS
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)
da927ba9 446 log_error_errno(r, "Failed to write %s: %m", database);
844ec79b
ZJS
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;
a7f7d1bd 549 struct stat st = {};
d4205751
LP
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) {
e53fc357 670 log_error_errno(k, "Failed to parse id128 '%s': %m", *item);
464264ac 671 if (r == 0)
54b7254c
ZJS
672 r = k;
673 continue;
674 }
675
844ec79b 676 k = catalog_get(database, id, &msg);
54b7254c 677 if (k < 0) {
e53fc357
LP
678 log_full_errno(k == -ENOENT ? LOG_NOTICE : LOG_ERR, k,
679 "Failed to retrieve catalog entry for '%s': %m", *item);
464264ac 680 if (r == 0)
54b7254c
ZJS
681 r = k;
682 continue;
683 }
684
685 dump_catalog_entry(f, id, msg, oneline);
686 }
687
688 return r;
689}