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