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