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