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