chiark / gitweb /
journal, shared: fix warnings during compilation on 32 bits
[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 #define CATALOG_PATH "/var/lib/systemd/catalog"
273 #define CATALOG_DATABASE CATALOG_PATH "/database"
274
275 int catalog_update(void) {
276         _cleanup_strv_free_ char **files = NULL;
277         _cleanup_fclose_ FILE *w = NULL;
278         _cleanup_free_ char *p = NULL;
279         char **f;
280         Hashmap *h;
281         struct strbuf *sb = NULL;
282         _cleanup_free_ CatalogItem *items = NULL;
283         CatalogItem *i;
284         CatalogHeader header;
285         size_t k;
286         Iterator j;
287         unsigned n;
288         int r;
289
290         h = hashmap_new(catalog_hash_func, catalog_compare_func);
291         if (!h)
292                 return -ENOMEM;
293
294         sb = strbuf_new();
295         if (!sb) {
296                 r = log_oom();
297                 goto finish;
298         }
299
300         r = conf_files_list_strv(&files, ".catalog", (const char **) conf_file_dirs);
301         if (r < 0) {
302                 log_error("Failed to get catalog files: %s", strerror(-r));
303                 goto finish;
304         }
305
306         STRV_FOREACH(f, files) {
307                 log_debug("reading file '%s'", *f);
308                 import_file(h, sb, *f);
309         }
310
311         if (hashmap_size(h) <= 0) {
312                 log_info("No items in catalog.");
313                 r = 0;
314                 goto finish;
315         } else
316                 log_debug("Found %u items in catalog.", hashmap_size(h));
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         r = mkdir_p(CATALOG_PATH, 0775);
336         if (r < 0) {
337                 log_error("Recursive mkdir %s: %s", CATALOG_PATH, strerror(-r));
338                 goto finish;
339         }
340
341         r = fopen_temporary(CATALOG_DATABASE, &w, &p);
342         if (r < 0) {
343                 log_error("Failed to open database for writing: %s: %s",
344                           CATALOG_DATABASE, strerror(-r));
345                 goto finish;
346         }
347
348         zero(header);
349         memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
350         header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
351         header.catalog_item_size = htole64(sizeof(CatalogItem));
352         header.n_items = htole64(hashmap_size(h));
353
354         k = fwrite(&header, 1, sizeof(header), w);
355         if (k != sizeof(header)) {
356                 log_error("%s: failed to write header.", p);
357                 goto finish;
358         }
359
360         k = fwrite(items, 1, n * sizeof(CatalogItem), w);
361         if (k != n * sizeof(CatalogItem)) {
362                 log_error("%s: failed to write database.", p);
363                 goto finish;
364         }
365
366         k = fwrite(sb->buf, 1, sb->len, w);
367         if (k != sb->len) {
368                 log_error("%s: failed to write strings.", p);
369                 goto finish;
370         }
371
372         fflush(w);
373
374         if (ferror(w)) {
375                 log_error("%s: failed to write database.", p);
376                 goto finish;
377         }
378
379         fchmod(fileno(w), 0644);
380
381         if (rename(p, CATALOG_DATABASE) < 0) {
382                 log_error("rename (%s -> %s) failed: %m", p, CATALOG_DATABASE);
383                 r = -errno;
384                 goto finish;
385         }
386
387         log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
388                  CATALOG_DATABASE, n, sb->len, ftell(w));
389
390         free(p);
391         p = NULL;
392
393         r = 0;
394
395 finish:
396         hashmap_free_free(h);
397
398         if (sb)
399                 strbuf_cleanup(sb);
400
401         if (p)
402                 unlink(p);
403
404         return r;
405 }
406
407 static int open_mmap(int *_fd, struct stat *_st, void **_p) {
408         const CatalogHeader *h;
409         int fd;
410         void *p;
411         struct stat st;
412
413         assert(_fd);
414         assert(_st);
415         assert(_p);
416
417         fd = open("/var/lib/systemd/catalog/database", O_RDONLY|O_CLOEXEC);
418         if (fd < 0)
419                 return -errno;
420
421         if (fstat(fd, &st) < 0) {
422                 close_nointr_nofail(fd);
423                 return -errno;
424         }
425
426         if (st.st_size < (off_t) sizeof(CatalogHeader)) {
427                 close_nointr_nofail(fd);
428                 return -EINVAL;
429         }
430
431         p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
432         if (p == MAP_FAILED) {
433                 close_nointr_nofail(fd);
434                 return -errno;
435         }
436
437         h = p;
438         if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
439             le64toh(h->header_size) < sizeof(CatalogHeader) ||
440             le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
441             h->incompatible_flags != 0 ||
442             le64toh(h->n_items) <= 0 ||
443             st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
444                 close_nointr_nofail(fd);
445                 munmap(p, st.st_size);
446                 return -EBADMSG;
447         }
448
449         *_fd = fd;
450         *_st = st;
451         *_p = p;
452
453         return 0;
454 }
455
456 static const char *find_id(void *p, sd_id128_t id) {
457         CatalogItem key, *f = NULL;
458         const CatalogHeader *h = p;
459         const char *loc;
460
461         zero(key);
462         key.id = id;
463
464         loc = setlocale(LC_MESSAGES, NULL);
465         if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
466                 strncpy(key.language, loc, sizeof(key.language));
467                 key.language[strcspn(key.language, ".@")] = 0;
468
469                 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
470                 if (!f) {
471                         char *e;
472
473                         e = strchr(key.language, '_');
474                         if (e) {
475                                 *e = 0;
476                                 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
477                         }
478                 }
479         }
480
481         if (!f) {
482                 zero(key.language);
483                 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
484         }
485
486         if (!f)
487                 return NULL;
488
489         return (const char*) p +
490                 le64toh(h->header_size) +
491                 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
492                 le64toh(f->offset);
493 }
494
495 int catalog_get(sd_id128_t id, char **_text) {
496         _cleanup_close_ int fd = -1;
497         void *p = NULL;
498         struct stat st;
499         char *text = NULL;
500         int r;
501         const char *s;
502
503         assert(_text);
504
505         r = open_mmap(&fd, &st, &p);
506         if (r < 0)
507                 return r;
508
509         s = find_id(p, id);
510         if (!s) {
511                 r = -ENOENT;
512                 goto finish;
513         }
514
515         text = strdup(s);
516         if (!text) {
517                 r = -ENOMEM;
518                 goto finish;
519         }
520
521         *_text = text;
522         r = 0;
523
524 finish:
525         if (p)
526                 munmap(p, st.st_size);
527
528         return r;
529 }
530
531 static char *find_header(const char *s, const char *header) {
532
533         for (;;) {
534                 const char *v, *e;
535
536                 v = startswith(s, header);
537                 if (v) {
538                         v += strspn(v, WHITESPACE);
539                         return strndup(v, strcspn(v, NEWLINE));
540                 }
541
542                 /* End of text */
543                 e = strchr(s, '\n');
544                 if (!e)
545                         return NULL;
546
547                 /* End of header */
548                 if (e == s)
549                         return NULL;
550
551                 s = e + 1;
552         }
553 }
554
555 int catalog_list(FILE *f) {
556         _cleanup_close_ int fd = -1;
557         void *p = NULL;
558         struct stat st;
559         const CatalogHeader *h;
560         const CatalogItem *items;
561         int r;
562         unsigned n;
563         sd_id128_t last_id;
564         bool last_id_set = false;
565
566         r = open_mmap(&fd, &st, &p);
567         if (r < 0)
568                 return r;
569
570         h = p;
571         items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
572
573         for (n = 0; n < le64toh(h->n_items); n++) {
574                 const char *s;
575                 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
576
577                 if (last_id_set && sd_id128_equal(last_id, items[n].id))
578                         continue;
579
580                 assert_se(s = find_id(p, items[n].id));
581
582                 subject = find_header(s, "Subject:");
583                 defined_by = find_header(s, "Defined-By:");
584
585                 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n", SD_ID128_FORMAT_VAL(items[n].id), strna(defined_by), strna(subject));
586
587                 last_id_set = true;
588                 last_id = items[n].id;
589         }
590
591         munmap(p, st.st_size);
592
593         return 0;
594 }