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