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