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