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