chiark / gitweb /
man: netdev - add some minimal explanation to the different netdev kinds and modes
[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 #include "siphash24.h"
42
43 const char * const catalog_file_dirs[] = {
44         "/usr/local/lib/systemd/catalog/",
45         "/usr/lib/systemd/catalog/",
46         NULL
47 };
48
49 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
50
51 typedef struct CatalogHeader {
52         uint8_t signature[8];  /* "RHHHKSLP" */
53         le32_t compatible_flags;
54         le32_t incompatible_flags;
55         le64_t header_size;
56         le64_t n_items;
57         le64_t catalog_item_size;
58 } CatalogHeader;
59
60 typedef struct CatalogItem {
61         sd_id128_t id;
62         char language[32];
63         le64_t offset;
64 } CatalogItem;
65
66 static unsigned long catalog_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) {
67         const CatalogItem *i = p;
68         uint64_t u;
69         size_t l, sz;
70         void *v;
71
72         l = strlen(i->language);
73         sz = sizeof(i->id) + l;
74         v = alloca(sz);
75
76         memcpy(mempcpy(v, &i->id, sizeof(i->id)), i->language, l);
77
78         siphash24((uint8_t*) &u, v, sz, hash_key);
79
80         return (unsigned long) u;
81 }
82
83 static int catalog_compare_func(const void *a, const void *b) {
84         const CatalogItem *i = a, *j = b;
85         unsigned k;
86
87         for (k = 0; k < ELEMENTSOF(j->id.bytes); k++) {
88                  if (i->id.bytes[k] < j->id.bytes[k])
89                         return -1;
90                  if (i->id.bytes[k] > j->id.bytes[k])
91                         return 1;
92         }
93
94         return strcmp(i->language, j->language);
95 }
96
97 const struct hash_ops catalog_hash_ops = {
98         .hash = catalog_hash_func,
99         .compare = catalog_compare_func
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         _cleanup_free_ CatalogItem *i = NULL;
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         if (language) {
127                 assert(strlen(language) > 1 && strlen(language) < 32);
128                 strcpy(i->language, language);
129         }
130         i->offset = htole64((uint64_t) offset);
131
132         r = hashmap_put(h, i, i);
133         if (r == -EEXIST) {
134                 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR ".%s, ignoring.",
135                             SD_ID128_FORMAT_VAL(id), language ? language : "C");
136                 return 0;
137         } else if (r < 0)
138                 return r;
139
140         i = NULL;
141         return 0;
142 }
143
144 int catalog_file_lang(const char* filename, char **lang) {
145         char *beg, *end, *_lang;
146
147         end = endswith(filename, ".catalog");
148         if (!end)
149                 return 0;
150
151         beg = end - 1;
152         while (beg > filename && *beg != '.' && *beg != '/' && end - beg < 32)
153                 beg --;
154
155         if (*beg != '.' || end <= beg + 1)
156                 return 0;
157
158         _lang = strndup(beg + 1, end - beg - 1);
159         if (!_lang)
160                 return -ENOMEM;
161
162         *lang = _lang;
163         return 1;
164 }
165
166 static int catalog_entry_lang(const char* filename, int line,
167                               const char* t, const char* deflang, char **lang) {
168         size_t c;
169
170         c = strlen(t);
171         if (c == 0) {
172                 log_error("[%s:%u] Language too short.", filename, line);
173                 return -EINVAL;
174         }
175         if (c > 31) {
176                 log_error("[%s:%u] language too long.", filename, line);
177                 return -EINVAL;
178         }
179
180         if (deflang) {
181                 if (streq(t, deflang)) {
182                         log_warning("[%s:%u] language specified unnecessarily",
183                                     filename, line);
184                         return 0;
185                 } else
186                         log_warning("[%s:%u] language differs from default for file",
187                                     filename, line);
188         }
189
190         *lang = strdup(t);
191         if (!*lang)
192                         return -ENOMEM;
193
194         return 0;
195 }
196
197 int catalog_import_file(Hashmap *h, struct strbuf *sb, const char *path) {
198         _cleanup_fclose_ FILE *f = NULL;
199         _cleanup_free_ char *payload = NULL;
200         unsigned n = 0;
201         sd_id128_t id;
202         _cleanup_free_ char *deflang = NULL, *lang = NULL;
203         bool got_id = false, empty_line = true;
204         int r;
205
206         assert(h);
207         assert(sb);
208         assert(path);
209
210         f = fopen(path, "re");
211         if (!f)
212                 return log_error_errno(errno, "Failed to open file %s: %m", path);
213
214         r = catalog_file_lang(path, &deflang);
215         if (r < 0)
216                 log_error_errno(errno, "Failed to determine language for file %s: %m", path);
217         if (r == 1)
218                 log_debug("File %s has language %s.", path, deflang);
219
220         for (;;) {
221                 char line[LINE_MAX];
222                 size_t a, b, c;
223                 char *t;
224
225                 if (!fgets(line, sizeof(line), f)) {
226                         if (feof(f))
227                                 break;
228
229                         log_error_errno(errno, "Failed to read file %s: %m", path);
230                         return -errno;
231                 }
232
233                 n++;
234
235                 truncate_nl(line);
236
237                 if (line[0] == 0) {
238                         empty_line = true;
239                         continue;
240                 }
241
242                 if (strchr(COMMENTS "\n", line[0]))
243                         continue;
244
245                 if (empty_line &&
246                     strlen(line) >= 2+1+32 &&
247                     line[0] == '-' &&
248                     line[1] == '-' &&
249                     line[2] == ' ' &&
250                     (line[2+1+32] == ' ' || line[2+1+32] == '\0')) {
251
252                         bool with_language;
253                         sd_id128_t jd;
254
255                         /* New entry */
256
257                         with_language = line[2+1+32] != '\0';
258                         line[2+1+32] = '\0';
259
260                         if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
261
262                                 if (got_id) {
263                                         r = finish_item(h, sb, id, lang ?: deflang, payload);
264                                         if (r < 0)
265                                                 return r;
266
267                                         free(lang);
268                                         lang = NULL;
269                                 }
270
271                                 if (with_language) {
272                                         t = strstrip(line + 2 + 1 + 32 + 1);
273
274                                         r = catalog_entry_lang(path, n, t, deflang, &lang);
275                                         if (r < 0)
276                                                 return r;
277                                 }
278
279                                 got_id = true;
280                                 empty_line = false;
281                                 id = jd;
282
283                                 if (payload)
284                                         payload[0] = '\0';
285
286                                 continue;
287                         }
288                 }
289
290                 /* Payload */
291                 if (!got_id) {
292                         log_error("[%s:%u] Got payload before ID.", path, n);
293                         return -EINVAL;
294                 }
295
296                 a = payload ? strlen(payload) : 0;
297                 b = strlen(line);
298
299                 c = a + (empty_line ? 1 : 0) + b + 1 + 1;
300                 t = realloc(payload, c);
301                 if (!t)
302                         return log_oom();
303
304                 if (empty_line) {
305                         t[a] = '\n';
306                         memcpy(t + a + 1, line, b);
307                         t[a+b+1] = '\n';
308                         t[a+b+2] = 0;
309                 } else {
310                         memcpy(t + a, line, b);
311                         t[a+b] = '\n';
312                         t[a+b+1] = 0;
313                 }
314
315                 payload = t;
316                 empty_line = false;
317         }
318
319         if (got_id) {
320                 r = finish_item(h, sb, id, lang ?: deflang, payload);
321                 if (r < 0)
322                         return r;
323         }
324
325         return 0;
326 }
327
328 static long write_catalog(const char *database, Hashmap *h, struct strbuf *sb,
329                           CatalogItem *items, size_t n) {
330         CatalogHeader header;
331         _cleanup_fclose_ FILE *w = NULL;
332         int r;
333         _cleanup_free_ char *d, *p = NULL;
334         size_t k;
335
336         d = dirname_malloc(database);
337         if (!d)
338                 return log_oom();
339
340         r = mkdir_p(d, 0775);
341         if (r < 0)
342                 return log_error_errno(r, "Recursive mkdir %s: %m", d);
343
344         r = fopen_temporary(database, &w, &p);
345         if (r < 0)
346                 return log_error_errno(r, "Failed to open database for writing: %s: %m",
347                                        database);
348
349         zero(header);
350         memcpy(header.signature, CATALOG_SIGNATURE, sizeof(header.signature));
351         header.header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8));
352         header.catalog_item_size = htole64(sizeof(CatalogItem));
353         header.n_items = htole64(hashmap_size(h));
354
355         r = -EIO;
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 error;
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 error;
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 error;
373         }
374
375         fflush(w);
376
377         if (ferror(w)) {
378                 log_error("%s: failed to write database.", p);
379                 goto error;
380         }
381
382         fchmod(fileno(w), 0644);
383
384         if (rename(p, database) < 0) {
385                 log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database);
386                 r = -errno;
387                 goto error;
388         }
389
390         return ftell(w);
391
392 error:
393         unlink(p);
394         return r;
395 }
396
397 int catalog_update(const char* database, const char* root, const char* const* dirs) {
398         _cleanup_strv_free_ char **files = NULL;
399         char **f;
400         struct strbuf *sb = NULL;
401         _cleanup_hashmap_free_free_ Hashmap *h = NULL;
402         _cleanup_free_ CatalogItem *items = NULL;
403         CatalogItem *i;
404         Iterator j;
405         unsigned n;
406         long r;
407
408         h = hashmap_new(&catalog_hash_ops);
409         sb = strbuf_new();
410
411         if (!h || !sb) {
412                 r = log_oom();
413                 goto finish;
414         }
415
416         r = conf_files_list_strv(&files, ".catalog", root, dirs);
417         if (r < 0) {
418                 log_error_errno(r, "Failed to get catalog files: %m");
419                 goto finish;
420         }
421
422         STRV_FOREACH(f, files) {
423                 log_debug("Reading file '%s'", *f);
424                 r = catalog_import_file(h, sb, *f);
425                 if (r < 0) {
426                         log_error("Failed to import file '%s': %s.",
427                                   *f, strerror(-r));
428                         goto finish;
429                 }
430         }
431
432         if (hashmap_size(h) <= 0) {
433                 log_info("No items in catalog.");
434                 goto finish;
435         } else
436                 log_debug("Found %u items in catalog.", hashmap_size(h));
437
438         strbuf_complete(sb);
439
440         items = new(CatalogItem, hashmap_size(h));
441         if (!items) {
442                 r = log_oom();
443                 goto finish;
444         }
445
446         n = 0;
447         HASHMAP_FOREACH(i, h, j) {
448                 log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
449                           SD_ID128_FORMAT_VAL(i->id),
450                           isempty(i->language) ? "C" : i->language);
451                 items[n++] = *i;
452         }
453
454         assert(n == hashmap_size(h));
455         qsort_safe(items, n, sizeof(CatalogItem), catalog_compare_func);
456
457         r = write_catalog(database, h, sb, items, n);
458         if (r < 0)
459                 log_error_errno(r, "Failed to write %s: %m", database);
460         else
461                 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
462                           database, n, sb->len, r);
463
464 finish:
465         if (sb)
466                 strbuf_cleanup(sb);
467
468         return r < 0 ? r : 0;
469 }
470
471 static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
472         const CatalogHeader *h;
473         int fd;
474         void *p;
475         struct stat st;
476
477         assert(_fd);
478         assert(_st);
479         assert(_p);
480
481         fd = open(database, O_RDONLY|O_CLOEXEC);
482         if (fd < 0)
483                 return -errno;
484
485         if (fstat(fd, &st) < 0) {
486                 safe_close(fd);
487                 return -errno;
488         }
489
490         if (st.st_size < (off_t) sizeof(CatalogHeader)) {
491                 safe_close(fd);
492                 return -EINVAL;
493         }
494
495         p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
496         if (p == MAP_FAILED) {
497                 safe_close(fd);
498                 return -errno;
499         }
500
501         h = p;
502         if (memcmp(h->signature, CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
503             le64toh(h->header_size) < sizeof(CatalogHeader) ||
504             le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
505             h->incompatible_flags != 0 ||
506             le64toh(h->n_items) <= 0 ||
507             st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
508                 safe_close(fd);
509                 munmap(p, st.st_size);
510                 return -EBADMSG;
511         }
512
513         *_fd = fd;
514         *_st = st;
515         *_p = p;
516
517         return 0;
518 }
519
520 static const char *find_id(void *p, sd_id128_t id) {
521         CatalogItem key, *f = NULL;
522         const CatalogHeader *h = p;
523         const char *loc;
524
525         zero(key);
526         key.id = id;
527
528         loc = setlocale(LC_MESSAGES, NULL);
529         if (loc && loc[0] && !streq(loc, "C") && !streq(loc, "POSIX")) {
530                 strncpy(key.language, loc, sizeof(key.language));
531                 key.language[strcspn(key.language, ".@")] = 0;
532
533                 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
534                 if (!f) {
535                         char *e;
536
537                         e = strchr(key.language, '_');
538                         if (e) {
539                                 *e = 0;
540                                 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
541                         }
542                 }
543         }
544
545         if (!f) {
546                 zero(key.language);
547                 f = bsearch(&key, (const uint8_t*) p + le64toh(h->header_size), le64toh(h->n_items), le64toh(h->catalog_item_size), catalog_compare_func);
548         }
549
550         if (!f)
551                 return NULL;
552
553         return (const char*) p +
554                 le64toh(h->header_size) +
555                 le64toh(h->n_items) * le64toh(h->catalog_item_size) +
556                 le64toh(f->offset);
557 }
558
559 int catalog_get(const char* database, sd_id128_t id, char **_text) {
560         _cleanup_close_ int fd = -1;
561         void *p = NULL;
562         struct stat st;
563         char *text = NULL;
564         int r;
565         const char *s;
566
567         assert(_text);
568
569         r = open_mmap(database, &fd, &st, &p);
570         if (r < 0)
571                 return r;
572
573         s = find_id(p, id);
574         if (!s) {
575                 r = -ENOENT;
576                 goto finish;
577         }
578
579         text = strdup(s);
580         if (!text) {
581                 r = -ENOMEM;
582                 goto finish;
583         }
584
585         *_text = text;
586         r = 0;
587
588 finish:
589         if (p)
590                 munmap(p, st.st_size);
591
592         return r;
593 }
594
595 static char *find_header(const char *s, const char *header) {
596
597         for (;;) {
598                 const char *v, *e;
599
600                 v = startswith(s, header);
601                 if (v) {
602                         v += strspn(v, WHITESPACE);
603                         return strndup(v, strcspn(v, NEWLINE));
604                 }
605
606                 /* End of text */
607                 e = strchr(s, '\n');
608                 if (!e)
609                         return NULL;
610
611                 /* End of header */
612                 if (e == s)
613                         return NULL;
614
615                 s = e + 1;
616         }
617 }
618
619 static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
620         if (oneline) {
621                 _cleanup_free_ char *subject = NULL, *defined_by = NULL;
622
623                 subject = find_header(s, "Subject:");
624                 defined_by = find_header(s, "Defined-By:");
625
626                 fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
627                         SD_ID128_FORMAT_VAL(id),
628                         strna(defined_by), strna(subject));
629         } else
630                 fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
631                         SD_ID128_FORMAT_VAL(id), s);
632 }
633
634
635 int catalog_list(FILE *f, const char *database, bool oneline) {
636         _cleanup_close_ int fd = -1;
637         void *p = NULL;
638         struct stat st;
639         const CatalogHeader *h;
640         const CatalogItem *items;
641         int r;
642         unsigned n;
643         sd_id128_t last_id;
644         bool last_id_set = false;
645
646         r = open_mmap(database, &fd, &st, &p);
647         if (r < 0)
648                 return r;
649
650         h = p;
651         items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
652
653         for (n = 0; n < le64toh(h->n_items); n++) {
654                 const char *s;
655
656                 if (last_id_set && sd_id128_equal(last_id, items[n].id))
657                         continue;
658
659                 assert_se(s = find_id(p, items[n].id));
660
661                 dump_catalog_entry(f, items[n].id, s, oneline);
662
663                 last_id_set = true;
664                 last_id = items[n].id;
665         }
666
667         munmap(p, st.st_size);
668
669         return 0;
670 }
671
672 int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
673         char **item;
674         int r = 0;
675
676         STRV_FOREACH(item, items) {
677                 sd_id128_t id;
678                 int k;
679                 _cleanup_free_ char *msg = NULL;
680
681                 k = sd_id128_from_string(*item, &id);
682                 if (k < 0) {
683                         log_error_errno(k, "Failed to parse id128 '%s': %m",
684                                         *item);
685                         if (r == 0)
686                                 r = k;
687                         continue;
688                 }
689
690                 k = catalog_get(database, id, &msg);
691                 if (k < 0) {
692                         log_full(k == -ENOENT ? LOG_NOTICE : LOG_ERR,
693                                  "Failed to retrieve catalog entry for '%s': %s",
694                                   *item, strerror(-k));
695                         if (r == 0)
696                                 r = k;
697                         continue;
698                 }
699
700                 dump_catalog_entry(f, id, msg, oneline);
701         }
702
703         return r;
704 }