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