]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/catalog.c
639e2d1b1bef7a6b21438b5dedcdf9921e19ac14
[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 int catalog_update(void) {
273 _cleanup_strv_free_ char **files = NULL;
274 _cleanup_fclose_ FILE *w = NULL;
275 _cleanup_free_ char *p = NULL;
276 char **f;
277 Hashmap *h;
278 struct strbuf *sb = NULL;
279 _cleanup_free_ CatalogItem *items = NULL;
280 CatalogItem *i;
281 CatalogHeader header;
282 size_t k;
283 Iterator j;
284 unsigned n;
285 int r;
286
287 h = hashmap_new(catalog_hash_func, catalog_compare_func);
288 if (!h)
289 return -ENOMEM;
290
291 sb = strbuf_new();
292 if (!sb) {
293 r = log_oom();
294 goto finish;
295 }
296
297 r = conf_files_list_strv(&files, ".catalog", (const char **) conf_file_dirs);
298 if (r < 0) {
299 log_error("Failed to get catalog files: %s", strerror(-r));
300 goto finish;
301 }
302
303 STRV_FOREACH(f, files) {
304 log_debug("reading file '%s'", *f);
305 import_file(h, sb, *f);
306 }
307
308 if (hashmap_size(h) <= 0) {
309 log_info("No items in catalog.");
310 r = 0;
311 goto finish;
312 }
313
314 strbuf_complete(sb);
315
316 items = new(CatalogItem, hashmap_size(h));
317 if (!items) {
318 r = log_oom();
319 goto finish;
320 }
321
322 n = 0;
323 HASHMAP_FOREACH(i, h, j) {
324 log_debug("Found " SD_ID128_FORMAT_STR ", language %s", SD_ID128_FORMAT_VAL(i->id), isempty(i->language) ? "C" : i->language);
325 items[n++] = *i;
326 }
327
328 assert(n == hashmap_size(h));
329 qsort(items, n, sizeof(CatalogItem), catalog_compare_func);
330
331 mkdir_p("/var/lib/systemd/catalog", 0775);
332
333 r = fopen_temporary("/var/lib/systemd/catalog/database", &w, &p);
334 if (r < 0) {
335 log_error("Failed to open database for writing: %s", strerror(-r));
336 goto finish;
337 }
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));
344
345 k = fwrite(&header, 1, sizeof(header), w);
346 if (k != sizeof(header)) {
347 log_error("Failed to write header.");
348 goto finish;
349 }
350
351 k = fwrite(items, 1, n * sizeof(CatalogItem), w);
352 if (k != n * sizeof(CatalogItem)) {
353 log_error("Failed to write database.");
354 goto finish;
355 }
356
357 k = fwrite(sb->buf, 1, sb->len, w);
358 if (k != sb->len) {
359 log_error("Failed to write strings.");
360 goto finish;
361 }
362
363 fflush(w);
364
365 if (ferror(w)) {
366 log_error("Failed to write database.");
367 goto finish;
368 }
369
370 fchmod(fileno(w), 0644);
371
372 if (rename(p, "/var/lib/systemd/catalog/database") < 0) {
373 log_error("rename() failed: %m");
374 r = -errno;
375 goto finish;
376 }
377
378 free(p);
379 p = NULL;
380
381 r = 0;
382
383 finish:
384 hashmap_free_free(h);
385
386 if (sb)
387 strbuf_cleanup(sb);
388
389 if (p)
390 unlink(p);
391
392 return r;
393 }
394
395 static int open_mmap(int *_fd, struct stat *_st, void **_p) {
396 const CatalogHeader *h;
397 int fd;
398 void *p;
399 struct stat st;
400
401 assert(_fd);
402 assert(_st);
403 assert(_p);
404
405 fd = open("/var/lib/systemd/catalog/database", O_RDONLY|O_CLOEXEC);
406 if (fd < 0)
407 return -errno;
408
409 if (fstat(fd, &st) < 0) {
410 close_nointr_nofail(fd);
411 return -errno;
412 }
413
414 if (st.st_size < (off_t) sizeof(CatalogHeader)) {
415 close_nointr_nofail(fd);
416 return -EINVAL;
417 }
418
419 p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
420 if (p == MAP_FAILED) {
421 close_nointr_nofail(fd);
422 return -errno;
423 }
424
425 h = p;
426 if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
427 le64toh(h->header_size) < sizeof(CatalogHeader) ||
428 le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
429 h->incompatible_flags != 0 ||
430 le64toh(h->n_items) <= 0 ||
431 st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
432 close_nointr_nofail(fd);
433 munmap(p, st.st_size);
434 return -EBADMSG;
435 }
436
437 *_fd = fd;
438 *_st = st;
439 *_p = p;
440
441 return 0;
442 }
443
444 static const char *find_id(void *p, sd_id128_t id) {
445 CatalogItem key, *f = NULL;
446 const CatalogHeader *h = p;
447 const char *loc;
448
449 zero(key);
450 key.id = id;
451
452 loc = setlocale(LC_MESSAGES, NULL);
453 if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
454 strncpy(key.language, loc, sizeof(key.language));
455 key.language[strcspn(key.language, ".@")] = 0;
456
457 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
458 if (!f) {
459 char *e;
460
461 e = strchr(key.language, '_');
462 if (e) {
463 *e = 0;
464 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
465 }
466 }
467 }
468
469 if (!f) {
470 zero(key.language);
471 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
472 }
473
474 if (!f)
475 return NULL;
476
477 return (const char*) p +
478 le64toh(h->header_size) +
479 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
480 le64toh(f->offset);
481 }
482
483 int catalog_get(sd_id128_t id, char **_text) {
484 _cleanup_close_ int fd = -1;
485 void *p = NULL;
486 struct stat st;
487 char *text = NULL;
488 int r;
489 const char *s;
490
491 assert(_text);
492
493 r = open_mmap(&fd, &st, &p);
494 if (r < 0)
495 return r;
496
497 s = find_id(p, id);
498 if (!s) {
499 r = -ENOENT;
500 goto finish;
501 }
502
503 text = strdup(s);
504 if (!text) {
505 r = -ENOMEM;
506 goto finish;
507 }
508
509 *_text = text;
510 r = 0;
511
512 finish:
513 if (p)
514 munmap(p, st.st_size);
515
516 return r;
517 }
518
519 static char *find_header(const char *s, const char *header) {
520
521 for (;;) {
522 const char *v, *e;
523
524 v = startswith(s, header);
525 if (v) {
526 v += strspn(v, WHITESPACE);
527 return strndup(v, strcspn(v, NEWLINE));
528 }
529
530 /* End of text */
531 e = strchr(s, '\n');
532 if (!e)
533 return NULL;
534
535 /* End of header */
536 if (e == s)
537 return NULL;
538
539 s = e + 1;
540 }
541 }
542
543 int catalog_list(FILE *f) {
544 _cleanup_close_ int fd = -1;
545 void *p = NULL;
546 struct stat st;
547 const CatalogHeader *h;
548 const CatalogItem *items;
549 int r;
550 unsigned n;
551 sd_id128_t last_id;
552 bool last_id_set = false;
553
554 r = open_mmap(&fd, &st, &p);
555 if (r < 0)
556 return r;
557
558 h = p;
559 items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
560
561 for (n = 0; n < le64toh(h->n_items); n++) {
562 const char *s;
563 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
564
565 if (last_id_set && sd_id128_equal(last_id, items[n].id))
566 continue;
567
568 assert_se(s = find_id(p, items[n].id));
569
570 subject = find_header(s, "Subject:");
571 defined_by = find_header(s, "Defined-By:");
572
573 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n", SD_ID128_FORMAT_VAL(items[n].id), strna(defined_by), strna(subject));
574
575 last_id_set = true;
576 last_id = items[n].id;
577 }
578
579 munmap(p, st.st_size);
580
581 return 0;
582 }