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