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