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