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