]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/catalog.c
journalctl: better error messages for --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
30 #include "util.h"
31 #include "log.h"
32 #include "sparse-endian.h"
33 #include "sd-id128.h"
34 #include "hashmap.h"
35 #include "strv.h"
36 #include "strbuf.h"
37 #include "conf-files.h"
38 #include "mkdir.h"
39 #include "catalog.h"
40
41 static const char * const conf_file_dirs[] = {
42 "/usr/local/lib/systemd/catalog/",
43 "/usr/lib/systemd/catalog/",
44 NULL
45 };
46
47 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
48
49 typedef struct CatalogHeader {
50 uint8_t signature[8]; /* "RHHHKSLP" */
51 le32_t compatible_flags;
52 le32_t incompatible_flags;
53 le64_t header_size;
54 le64_t n_items;
55 le64_t catalog_item_size;
56 } CatalogHeader;
57
58 typedef struct CatalogItem {
59 sd_id128_t id;
60 char language[32];
61 le64_t offset;
62 } CatalogItem;
63
64 static unsigned catalog_hash_func(const void *p) {
65 const CatalogItem *i = p;
66
67 assert_cc(sizeof(unsigned) == sizeof(uint8_t)*4);
68
69 return (((unsigned) i->id.bytes[0] << 24) |
70 ((unsigned) i->id.bytes[1] << 16) |
71 ((unsigned) i->id.bytes[2] << 8) |
72 ((unsigned) i->id.bytes[3])) ^
73 (((unsigned) i->id.bytes[4] << 24) |
74 ((unsigned) i->id.bytes[5] << 16) |
75 ((unsigned) i->id.bytes[6] << 8) |
76 ((unsigned) i->id.bytes[7])) ^
77 (((unsigned) i->id.bytes[8] << 24) |
78 ((unsigned) i->id.bytes[9] << 16) |
79 ((unsigned) i->id.bytes[10] << 8) |
80 ((unsigned) i->id.bytes[11])) ^
81 (((unsigned) i->id.bytes[12] << 24) |
82 ((unsigned) i->id.bytes[13] << 16) |
83 ((unsigned) i->id.bytes[14] << 8) |
84 ((unsigned) i->id.bytes[15])) ^
85 string_hash_func(i->language);
86 }
87
88 static int catalog_compare_func(const void *a, const void *b) {
89 const CatalogItem *i = a, *j = b;
90 unsigned k;
91
92 for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) {
93 if (i->id.bytes[k] < j->id.bytes[k])
94 return -1;
95 if (i->id.bytes[k] > j->id.bytes[k])
96 return 1;
97 }
98
99 return strncmp(i->language, j->language, sizeof(i->language));
100 }
101
102 static int finish_item(
103 Hashmap *h,
104 struct strbuf *sb,
105 sd_id128_t id,
106 const char *language,
107 const char *payload) {
108
109 ssize_t offset;
110 CatalogItem *i;
111 int r;
112
113 assert(h);
114 assert(sb);
115 assert(payload);
116
117 offset = strbuf_add_string(sb, payload, strlen(payload));
118 if (offset < 0)
119 return log_oom();
120
121 i = new0(CatalogItem, 1);
122 if (!i)
123 return log_oom();
124
125 i->id = id;
126 strncpy(i->language, language, sizeof(i->language));
127 i->offset = htole64((uint64_t) offset);
128
129 r = hashmap_put(h, i, i);
130 if (r == EEXIST) {
131 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.", SD_ID128_FORMAT_VAL(id), language ? language : "C");
132 free(i);
133 return 0;
134 }
135
136 return 0;
137 }
138
139 static int import_file(Hashmap *h, struct strbuf *sb, const char *path) {
140 _cleanup_fclose_ FILE *f = NULL;
141 _cleanup_free_ char *payload = NULL;
142 unsigned n = 0;
143 sd_id128_t id;
144 char language[32];
145 bool got_id = false, empty_line = true;
146 int r;
147
148 assert(h);
149 assert(sb);
150 assert(path);
151
152 f = fopen(path, "re");
153 if (!f) {
154 log_error("Failed to open file %s: %m", path);
155 return -errno;
156 }
157
158 for (;;) {
159 char line[LINE_MAX];
160 size_t a, b, c;
161 char *t;
162
163 if (!fgets(line, sizeof(line), f)) {
164 if (feof(f))
165 break;
166
167 log_error("Failed to read file %s: %m", path);
168 return -errno;
169 }
170
171 n++;
172
173 truncate_nl(line);
174
175 if (line[0] == 0) {
176 empty_line = true;
177 continue;
178 }
179
180 if (strchr(COMMENTS, line[0]))
181 continue;
182
183 if (empty_line &&
184 strlen(line) >= 2+1+32 &&
185 line[0] == '-' &&
186 line[1] == '-' &&
187 line[2] == ' ' &&
188 (line[2+1+32] == ' ' || line[2+1+32] == 0)) {
189
190 bool with_language;
191 sd_id128_t jd;
192
193 /* New entry */
194
195 with_language = line[2+1+32] != 0;
196 line[2+1+32] = 0;
197
198 if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
199
200 if (got_id) {
201 r = finish_item(h, sb, id, language, payload);
202 if (r < 0)
203 return r;
204 }
205
206 if (with_language) {
207 t = strstrip(line + 2 + 1 + 32 + 1);
208
209 c = strlen(t);
210 if (c <= 0) {
211 log_error("[%s:%u] Language too short.", path, n);
212 return -EINVAL;
213 }
214 if (c > sizeof(language)) {
215 log_error("[%s:%u] language too long.", path, n);
216 return -EINVAL;
217 }
218
219 strncpy(language, t, sizeof(language));
220 } else
221 zero(language);
222
223 got_id = true;
224 empty_line = false;
225 id = jd;
226
227 if (payload)
228 payload[0] = 0;
229
230 continue;
231 }
232 }
233
234 /* Payload */
235 if (!got_id) {
236 log_error("[%s:%u] Got payload before ID.", path, n);
237 return -EINVAL;
238 }
239
240 a = payload ? strlen(payload) : 0;
241 b = strlen(line);
242
243 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
244 t = realloc(payload, c);
245 if (!t)
246 return log_oom();
247
248 if (empty_line) {
249 t[a] = '\n';
250 memcpy(t + a + 1, line, b);
251 t[a+b+1] = '\n';
252 t[a+b+2] = 0;
253 } else {
254 memcpy(t + a, line, b);
255 t[a+b] = '\n';
256 t[a+b+1] = 0;
257 }
258
259 payload = t;
260 empty_line = false;
261 }
262
263 if (got_id) {
264 r = finish_item(h, sb, id, language, payload);
265 if (r < 0)
266 return r;
267 }
268
269 return 0;
270 }
271
272 #define CATALOG_PATH "/var/lib/systemd/catalog"
273 #define CATALOG_DATABASE CATALOG_PATH "/database"
274
275 int catalog_update(void) {
276 _cleanup_strv_free_ char **files = NULL;
277 _cleanup_fclose_ FILE *w = NULL;
278 _cleanup_free_ char *p = NULL;
279 char **f;
280 Hashmap *h;
281 struct strbuf *sb = NULL;
282 _cleanup_free_ CatalogItem *items = NULL;
283 CatalogItem *i;
284 CatalogHeader header;
285 size_t k;
286 Iterator j;
287 unsigned n;
288 int r;
289
290 h = hashmap_new(catalog_hash_func, catalog_compare_func);
291 if (!h)
292 return -ENOMEM;
293
294 sb = strbuf_new();
295 if (!sb) {
296 r = log_oom();
297 goto finish;
298 }
299
300 r = conf_files_list_strv(&files, ".catalog", (const char **) conf_file_dirs);
301 if (r < 0) {
302 log_error("Failed to get catalog files: %s", strerror(-r));
303 goto finish;
304 }
305
306 STRV_FOREACH(f, files) {
307 log_debug("reading file '%s'", *f);
308 import_file(h, sb, *f);
309 }
310
311 if (hashmap_size(h) <= 0) {
312 log_info("No items in catalog.");
313 r = 0;
314 goto finish;
315 } else
316 log_debug("Found %u items in catalog.", hashmap_size(h));
317
318 strbuf_complete(sb);
319
320 items = new(CatalogItem, hashmap_size(h));
321 if (!items) {
322 r = log_oom();
323 goto finish;
324 }
325
326 n = 0;
327 HASHMAP_FOREACH(i, h, j) {
328 log_debug("Found " SD_ID128_FORMAT_STR ", language %s", SD_ID128_FORMAT_VAL(i->id), isempty(i->language) ? "C" : i->language);
329 items[n++] = *i;
330 }
331
332 assert(n == hashmap_size(h));
333 qsort(items, n, sizeof(CatalogItem), catalog_compare_func);
334
335 r = mkdir_p(CATALOG_PATH, 0775);
336 if (r < 0) {
337 log_error("Recursive mkdir %s: %s", CATALOG_PATH, strerror(-r));
338 goto finish;
339 }
340
341 r = fopen_temporary(CATALOG_DATABASE, &w, &p);
342 if (r < 0) {
343 log_error("Failed to open database for writing: %s: %s",
344 CATALOG_DATABASE, strerror(-r));
345 goto finish;
346 }
347
348 zero(header);
349 memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
350 header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
351 header.catalog_item_size = htole64(sizeof(CatalogItem));
352 header.n_items = htole64(hashmap_size(h));
353
354 k = fwrite(&header, 1, sizeof(header), w);
355 if (k != sizeof(header)) {
356 log_error("%s: failed to write header.", p);
357 goto finish;
358 }
359
360 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
361 if (k != n * sizeof(CatalogItem)) {
362 log_error("%s: failed to write database.", p);
363 goto finish;
364 }
365
366 k = fwrite(sb->buf, 1, sb->len, w);
367 if (k != sb->len) {
368 log_error("%s: failed to write strings.", p);
369 goto finish;
370 }
371
372 fflush(w);
373
374 if (ferror(w)) {
375 log_error("%s: failed to write database.", p);
376 goto finish;
377 }
378
379 fchmod(fileno(w), 0644);
380
381 if (rename(p, CATALOG_DATABASE) < 0) {
382 log_error("rename (%s -> %s) failed: %m", p, CATALOG_DATABASE);
383 r = -errno;
384 goto finish;
385 }
386
387 log_info("%s: wrote %u items, with %zu bytes of strings, %zu total size.",
388 CATALOG_DATABASE, n, sb->len, ftell(w));
389
390 free(p);
391 p = NULL;
392
393 r = 0;
394
395 finish:
396 hashmap_free_free(h);
397
398 if (sb)
399 strbuf_cleanup(sb);
400
401 if (p)
402 unlink(p);
403
404 return r;
405 }
406
407 static int open_mmap(int *_fd, struct stat *_st, void **_p) {
408 const CatalogHeader *h;
409 int fd;
410 void *p;
411 struct stat st;
412
413 assert(_fd);
414 assert(_st);
415 assert(_p);
416
417 fd = open("/var/lib/systemd/catalog/database", O_RDONLY|O_CLOEXEC);
418 if (fd < 0)
419 return -errno;
420
421 if (fstat(fd, &st) < 0) {
422 close_nointr_nofail(fd);
423 return -errno;
424 }
425
426 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
427 close_nointr_nofail(fd);
428 return -EINVAL;
429 }
430
431 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
432 if (p == MAP_FAILED) {
433 close_nointr_nofail(fd);
434 return -errno;
435 }
436
437 h = p;
438 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
439 le64toh(h->header_size) < sizeof(CatalogHeader) ||
440 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
441 h->incompatible_flags != 0 ||
442 le64toh(h->n_items) <= 0 ||
443 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
444 close_nointr_nofail(fd);
445 munmap(p, st.st_size);
446 return -EBADMSG;
447 }
448
449 *_fd = fd;
450 *_st = st;
451 *_p = p;
452
453 return 0;
454 }
455
456 static const char *find_id(void *p, sd_id128_t id) {
457 CatalogItem key, *f = NULL;
458 const CatalogHeader *h = p;
459 const char *loc;
460
461 zero(key);
462 key.id = id;
463
464 loc = setlocale(LC_MESSAGES, NULL);
465 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
466 strncpy(key.language, loc, sizeof(key.language));
467 key.language[strcspn(key.language, ".@")] = 0;
468
469 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
470 if (!f) {
471 char *e;
472
473 e = strchr(key.language, '_');
474 if (e) {
475 *e = 0;
476 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
477 }
478 }
479 }
480
481 if (!f) {
482 zero(key.language);
483 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
484 }
485
486 if (!f)
487 return NULL;
488
489 return (const char*) p +
490 le64toh(h->header_size) +
491 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
492 le64toh(f->offset);
493 }
494
495 int catalog_get(sd_id128_t id, char **_text) {
496 _cleanup_close_ int fd = -1;
497 void *p = NULL;
498 struct stat st;
499 char *text = NULL;
500 int r;
501 const char *s;
502
503 assert(_text);
504
505 r = open_mmap(&fd, &st, &p);
506 if (r < 0)
507 return r;
508
509 s = find_id(p, id);
510 if (!s) {
511 r = -ENOENT;
512 goto finish;
513 }
514
515 text = strdup(s);
516 if (!text) {
517 r = -ENOMEM;
518 goto finish;
519 }
520
521 *_text = text;
522 r = 0;
523
524 finish:
525 if (p)
526 munmap(p, st.st_size);
527
528 return r;
529 }
530
531 static char *find_header(const char *s, const char *header) {
532
533 for (;;) {
534 const char *v, *e;
535
536 v = startswith(s, header);
537 if (v) {
538 v += strspn(v, WHITESPACE);
539 return strndup(v, strcspn(v, NEWLINE));
540 }
541
542 /* End of text */
543 e = strchr(s, '\n');
544 if (!e)
545 return NULL;
546
547 /* End of header */
548 if (e == s)
549 return NULL;
550
551 s = e + 1;
552 }
553 }
554
555 int catalog_list(FILE *f) {
556 _cleanup_close_ int fd = -1;
557 void *p = NULL;
558 struct stat st;
559 const CatalogHeader *h;
560 const CatalogItem *items;
561 int r;
562 unsigned n;
563 sd_id128_t last_id;
564 bool last_id_set = false;
565
566 r = open_mmap(&fd, &st, &p);
567 if (r < 0)
568 return r;
569
570 h = p;
571 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
572
573 for (n = 0; n < le64toh(h->n_items); n++) {
574 const char *s;
575 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
576
577 if (last_id_set && sd_id128_equal(last_id, items[n].id))
578 continue;
579
580 assert_se(s = find_id(p, items[n].id));
581
582 subject = find_header(s, "Subject:");
583 defined_by = find_header(s, "Defined-By:");
584
585 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n", SD_ID128_FORMAT_VAL(items[n].id), strna(defined_by), strna(subject));
586
587 last_id_set = true;
588 last_id = items[n].id;
589 }
590
591 munmap(p, st.st_size);
592
593 return 0;
594 }