Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <errno.h>
4 : : #include <fcntl.h>
5 : : #include <locale.h>
6 : : #include <stdio.h>
7 : : #include <string.h>
8 : : #include <sys/mman.h>
9 : : #include <sys/stat.h>
10 : : #include <sys/types.h>
11 : : #include <unistd.h>
12 : :
13 : : #include "sd-id128.h"
14 : :
15 : : #include "alloc-util.h"
16 : : #include "catalog.h"
17 : : #include "conf-files.h"
18 : : #include "fd-util.h"
19 : : #include "fileio.h"
20 : : #include "hashmap.h"
21 : : #include "log.h"
22 : : #include "memory-util.h"
23 : : #include "mkdir.h"
24 : : #include "path-util.h"
25 : : #include "siphash24.h"
26 : : #include "sort-util.h"
27 : : #include "sparse-endian.h"
28 : : #include "strbuf.h"
29 : : #include "string-util.h"
30 : : #include "strv.h"
31 : : #include "tmpfile-util.h"
32 : :
33 : : const char * const catalog_file_dirs[] = {
34 : : "/usr/local/lib/systemd/catalog/",
35 : : "/usr/lib/systemd/catalog/",
36 : : NULL
37 : : };
38 : :
39 : : #define CATALOG_SIGNATURE { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
40 : :
41 : : typedef struct CatalogHeader {
42 : : uint8_t signature[8]; /* "RHHHKSLP" */
43 : : le32_t compatible_flags;
44 : : le32_t incompatible_flags;
45 : : le64_t header_size;
46 : : le64_t n_items;
47 : : le64_t catalog_item_size;
48 : : } CatalogHeader;
49 : :
50 : : typedef struct CatalogItem {
51 : : sd_id128_t id;
52 : : char language[32]; /* One byte is used for termination, so the maximum allowed
53 : : * length of the string is actually 31 bytes. */
54 : : le64_t offset;
55 : : } CatalogItem;
56 : :
57 : 4540 : static void catalog_hash_func(const CatalogItem *i, struct siphash *state) {
58 : 4540 : siphash24_compress(&i->id, sizeof(i->id), state);
59 : 4540 : siphash24_compress(i->language, strlen(i->language), state);
60 : 4540 : }
61 : :
62 : 15038 : static int catalog_compare_func(const CatalogItem *a, const CatalogItem *b) {
63 : : unsigned k;
64 : : int r;
65 : :
66 [ + + ]: 86406 : for (k = 0; k < ELEMENTSOF(b->id.bytes); k++) {
67 [ + + ]: 81958 : r = CMP(a->id.bytes[k], b->id.bytes[k]);
68 [ + + ]: 81958 : if (r != 0)
69 : 10590 : return r;
70 : : }
71 : :
72 : 4448 : return strcmp(a->language, b->language);
73 : : }
74 : :
75 : : DEFINE_HASH_OPS(catalog_hash_ops, CatalogItem, catalog_hash_func, catalog_compare_func);
76 : :
77 : 212 : static bool next_header(const char **s) {
78 : : const char *e;
79 : :
80 : 212 : e = strchr(*s, '\n');
81 : :
82 : : /* Unexpected end */
83 [ + + ]: 212 : if (!e)
84 : 4 : return false;
85 : :
86 : : /* End of headers */
87 [ + + ]: 208 : if (e == *s)
88 : 12 : return false;
89 : :
90 : 196 : *s = e + 1;
91 : 196 : return true;
92 : : }
93 : :
94 : 16 : static const char *skip_header(const char *s) {
95 [ + + ]: 48 : while (next_header(&s))
96 : : ;
97 : 16 : return s;
98 : : }
99 : :
100 : 8 : static char *combine_entries(const char *one, const char *two) {
101 : : const char *b1, *b2;
102 : : size_t l1, l2, n;
103 : : char *dest, *p;
104 : :
105 : : /* Find split point of headers to body */
106 : 8 : b1 = skip_header(one);
107 : 8 : b2 = skip_header(two);
108 : :
109 : 8 : l1 = strlen(one);
110 : 8 : l2 = strlen(two);
111 : 8 : dest = new(char, l1 + l2 + 1);
112 [ - + ]: 8 : if (!dest) {
113 : 0 : log_oom();
114 : 0 : return NULL;
115 : : }
116 : :
117 : 8 : p = dest;
118 : :
119 : : /* Headers from @one */
120 : 8 : n = b1 - one;
121 : 8 : p = mempcpy(p, one, n);
122 : :
123 : : /* Headers from @two, these will only be found if not present above */
124 : 8 : n = b2 - two;
125 : 8 : p = mempcpy(p, two, n);
126 : :
127 : : /* Body from @one */
128 : 8 : n = l1 - (b1 - one);
129 [ + + ]: 8 : if (n > 0) {
130 : 4 : memcpy(p, b1, n);
131 : 4 : p += n;
132 : :
133 : : /* Body from @two */
134 : : } else {
135 : 4 : n = l2 - (b2 - two);
136 : 4 : memcpy(p, b2, n);
137 : 4 : p += n;
138 : : }
139 : :
140 [ - + ]: 8 : assert(p - dest <= (ptrdiff_t)(l1 + l2));
141 : 8 : p[0] = '\0';
142 : 8 : return dest;
143 : : }
144 : :
145 : 1496 : static int finish_item(
146 : : Hashmap *h,
147 : : sd_id128_t id,
148 : : const char *language,
149 : : char *payload, size_t payload_size) {
150 : :
151 : 1496 : _cleanup_free_ CatalogItem *i = NULL;
152 : 1496 : _cleanup_free_ char *prev = NULL, *combined = NULL;
153 : :
154 [ - + ]: 1496 : assert(h);
155 [ - + ]: 1496 : assert(payload);
156 [ - + ]: 1496 : assert(payload_size > 0);
157 : :
158 : 1496 : i = new0(CatalogItem, 1);
159 [ - + ]: 1496 : if (!i)
160 : 0 : return log_oom();
161 : :
162 : 1496 : i->id = id;
163 [ + + ]: 1496 : if (language) {
164 [ + - - + ]: 1332 : assert(strlen(language) > 1 && strlen(language) < 32);
165 : 1332 : strcpy(i->language, language);
166 : : }
167 : :
168 : 1496 : prev = hashmap_get(h, i);
169 [ + + ]: 1496 : if (prev) {
170 : : /* Already have such an item, combine them */
171 : 8 : combined = combine_entries(payload, prev);
172 [ - + ]: 8 : if (!combined)
173 : 0 : return log_oom();
174 : :
175 [ - + ]: 8 : if (hashmap_update(h, i, combined) < 0)
176 : 0 : return log_oom();
177 : 8 : combined = NULL;
178 : : } else {
179 : : /* A new item */
180 : 1488 : combined = memdup(payload, payload_size + 1);
181 [ - + ]: 1488 : if (!combined)
182 : 0 : return log_oom();
183 : :
184 [ - + ]: 1488 : if (hashmap_put(h, i, combined) < 0)
185 : 0 : return log_oom();
186 : 1488 : i = NULL;
187 : 1488 : combined = NULL;
188 : : }
189 : :
190 : 1496 : return 0;
191 : : }
192 : :
193 : 100 : int catalog_file_lang(const char* filename, char **lang) {
194 : : char *beg, *end, *_lang;
195 : :
196 : 100 : end = endswith(filename, ".catalog");
197 [ + + ]: 100 : if (!end)
198 : 24 : return 0;
199 : :
200 : 76 : beg = end - 1;
201 [ + - + + : 576 : while (beg > filename && !IN_SET(*beg, '.', '/') && end - beg < 32)
+ + + + ]
202 : 500 : beg--;
203 : :
204 [ + + + + ]: 76 : if (*beg != '.' || end <= beg + 1)
205 : 16 : return 0;
206 : :
207 : 60 : _lang = strndup(beg + 1, end - beg - 1);
208 [ - + ]: 60 : if (!_lang)
209 : 0 : return -ENOMEM;
210 : :
211 : 60 : *lang = _lang;
212 : 60 : return 1;
213 : : }
214 : :
215 : 28 : static int catalog_entry_lang(
216 : : const char* filename,
217 : : unsigned line,
218 : : const char* t,
219 : : const char* deflang,
220 : : char **ret) {
221 : :
222 : : size_t c;
223 : : char *z;
224 : :
225 : 28 : c = strlen(t);
226 [ - + ]: 28 : if (c < 2)
227 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
228 : : "[%s:%u] Language too short.", filename, line);
229 [ + + ]: 28 : if (c > 31)
230 [ + - ]: 4 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
231 : : "[%s:%u] language too long.", filename, line);
232 : :
233 [ - + ]: 24 : if (deflang) {
234 [ # # ]: 0 : if (streq(t, deflang)) {
235 [ # # ]: 0 : log_warning("[%s:%u] language specified unnecessarily", filename, line);
236 : 0 : return 0;
237 : : }
238 : :
239 [ # # ]: 0 : log_warning("[%s:%u] language differs from default for file", filename, line);
240 : : }
241 : :
242 : 24 : z = strdup(t);
243 [ - + ]: 24 : if (!z)
244 : 0 : return -ENOMEM;
245 : :
246 : 24 : *ret = z;
247 : 24 : return 0;
248 : : }
249 : :
250 : 68 : int catalog_import_file(Hashmap *h, const char *path) {
251 : 68 : _cleanup_fclose_ FILE *f = NULL;
252 : 68 : _cleanup_free_ char *payload = NULL;
253 : 68 : size_t payload_size = 0, payload_allocated = 0;
254 : 68 : unsigned n = 0;
255 : : sd_id128_t id;
256 : 68 : _cleanup_free_ char *deflang = NULL, *lang = NULL;
257 : 68 : bool got_id = false, empty_line = true;
258 : : int r;
259 : :
260 [ - + ]: 68 : assert(h);
261 [ - + ]: 68 : assert(path);
262 : :
263 : 68 : f = fopen(path, "re");
264 [ - + ]: 68 : if (!f)
265 [ # # ]: 0 : return log_error_errno(errno, "Failed to open file %s: %m", path);
266 : :
267 : 68 : r = catalog_file_lang(path, &deflang);
268 [ - + ]: 68 : if (r < 0)
269 [ # # ]: 0 : log_error_errno(r, "Failed to determine language for file %s: %m", path);
270 [ + + ]: 68 : if (r == 1)
271 [ + - ]: 44 : log_debug("File %s has language %s.", path, deflang);
272 : :
273 : 14768 : for (;;) {
274 [ + + + + ]: 14836 : _cleanup_free_ char *line = NULL;
275 : : size_t line_len;
276 : :
277 : 14836 : r = read_line(f, LONG_LINE_MAX, &line);
278 [ - + ]: 14836 : if (r < 0)
279 [ # # ]: 0 : return log_error_errno(r, "Failed to read file %s: %m", path);
280 [ + + ]: 14836 : if (r == 0)
281 : 60 : break;
282 : :
283 : 14776 : n++;
284 : :
285 [ + + ]: 14776 : if (isempty(line)) {
286 : 3776 : empty_line = true;
287 : 3776 : continue;
288 : : }
289 : :
290 [ + + ]: 11000 : if (strchr(COMMENTS, line[0]))
291 : 624 : continue;
292 : :
293 [ + + ]: 10376 : if (empty_line &&
294 [ + + ]: 3676 : strlen(line) >= 2+1+32 &&
295 [ + + ]: 3480 : line[0] == '-' &&
296 [ + - ]: 1500 : line[1] == '-' &&
297 [ + - ]: 1500 : line[2] == ' ' &&
298 [ + - + - ]: 1500 : IN_SET(line[2+1+32], ' ', '\0')) {
299 : :
300 : : bool with_language;
301 : : sd_id128_t jd;
302 : :
303 : : /* New entry */
304 : :
305 : 1500 : with_language = line[2+1+32] != '\0';
306 : 1500 : line[2+1+32] = '\0';
307 : :
308 [ + - ]: 1500 : if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
309 : :
310 [ + + ]: 1500 : if (got_id) {
311 [ - + ]: 1436 : if (payload_size == 0)
312 [ # # ]: 4 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
313 : : "[%s:%u] No payload text.",
314 : : path,
315 : : n);
316 : :
317 [ + + ]: 1436 : r = finish_item(h, id, lang ?: deflang, payload, payload_size);
318 [ - + ]: 1436 : if (r < 0)
319 : 0 : return r;
320 : :
321 : 1436 : lang = mfree(lang);
322 : 1436 : payload_size = 0;
323 : : }
324 : :
325 [ + + ]: 1500 : if (with_language) {
326 : : char *t;
327 : :
328 : 28 : t = strstrip(line + 2 + 1 + 32 + 1);
329 : 28 : r = catalog_entry_lang(path, n, t, deflang, &lang);
330 [ + + ]: 28 : if (r < 0)
331 : 4 : return r;
332 : : }
333 : :
334 : 1496 : got_id = true;
335 : 1496 : empty_line = false;
336 : 1496 : id = jd;
337 : :
338 : 1496 : continue;
339 : : }
340 : : }
341 : :
342 : : /* Payload */
343 [ + + ]: 8876 : if (!got_id)
344 [ + - ]: 4 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
345 : : "[%s:%u] Got payload before ID.",
346 : : path, n);
347 : :
348 : 8872 : line_len = strlen(line);
349 [ + + - + ]: 8872 : if (!GREEDY_REALLOC(payload, payload_allocated,
350 : : payload_size + (empty_line ? 1 : 0) + line_len + 1 + 1))
351 : 0 : return log_oom();
352 : :
353 [ + + ]: 8872 : if (empty_line)
354 : 2172 : payload[payload_size++] = '\n';
355 : 8872 : memcpy(payload + payload_size, line, line_len);
356 : 8872 : payload_size += line_len;
357 : 8872 : payload[payload_size++] = '\n';
358 : 8872 : payload[payload_size] = '\0';
359 : :
360 : 8872 : empty_line = false;
361 : : }
362 : :
363 [ + - ]: 60 : if (got_id) {
364 [ - + ]: 60 : if (payload_size == 0)
365 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
366 : : "[%s:%u] No payload text.",
367 : : path, n);
368 : :
369 [ + + ]: 60 : r = finish_item(h, id, lang ?: deflang, payload, payload_size);
370 [ - + ]: 60 : if (r < 0)
371 : 0 : return r;
372 : : }
373 : :
374 : 60 : return 0;
375 : : }
376 : :
377 : 4 : static int64_t write_catalog(
378 : : const char *database,
379 : : struct strbuf *sb,
380 : : CatalogItem *items,
381 : : size_t n) {
382 : :
383 : 4 : _cleanup_fclose_ FILE *w = NULL;
384 : 4 : _cleanup_free_ char *p = NULL;
385 : : CatalogHeader header;
386 : : size_t k;
387 : : int r;
388 : :
389 : 4 : r = mkdir_parents(database, 0755);
390 [ - + ]: 4 : if (r < 0)
391 [ # # ]: 0 : return log_error_errno(r, "Failed to create parent directories of %s: %m", database);
392 : :
393 : 4 : r = fopen_temporary(database, &w, &p);
394 [ - + ]: 4 : if (r < 0)
395 [ # # ]: 0 : return log_error_errno(r, "Failed to open database for writing: %s: %m",
396 : : database);
397 : :
398 : 4 : header = (CatalogHeader) {
399 : : .signature = CATALOG_SIGNATURE,
400 : 4 : .header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8)),
401 : 4 : .catalog_item_size = htole64(sizeof(CatalogItem)),
402 : 4 : .n_items = htole64(n),
403 : : };
404 : :
405 : 4 : r = -EIO;
406 : :
407 : 4 : k = fwrite(&header, 1, sizeof(header), w);
408 [ - + ]: 4 : if (k != sizeof(header)) {
409 [ # # ]: 0 : log_error("%s: failed to write header.", p);
410 : 0 : goto error;
411 : : }
412 : :
413 : 4 : k = fwrite(items, 1, n * sizeof(CatalogItem), w);
414 [ - + ]: 4 : if (k != n * sizeof(CatalogItem)) {
415 [ # # ]: 0 : log_error("%s: failed to write database.", p);
416 : 0 : goto error;
417 : : }
418 : :
419 : 4 : k = fwrite(sb->buf, 1, sb->len, w);
420 [ - + ]: 4 : if (k != sb->len) {
421 [ # # ]: 0 : log_error("%s: failed to write strings.", p);
422 : 0 : goto error;
423 : : }
424 : :
425 : 4 : r = fflush_and_check(w);
426 [ - + ]: 4 : if (r < 0) {
427 [ # # ]: 0 : log_error_errno(r, "%s: failed to write database: %m", p);
428 : 0 : goto error;
429 : : }
430 : :
431 : 4 : (void) fchmod(fileno(w), 0644);
432 : :
433 [ - + ]: 4 : if (rename(p, database) < 0) {
434 [ # # ]: 0 : r = log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database);
435 : 0 : goto error;
436 : : }
437 : :
438 : 4 : return ftello(w);
439 : :
440 : 0 : error:
441 : 0 : (void) unlink(p);
442 : 0 : return r;
443 : : }
444 : :
445 : 12 : int catalog_update(const char* database, const char* root, const char* const* dirs) {
446 : 12 : _cleanup_strv_free_ char **files = NULL;
447 : : char **f;
448 : 12 : _cleanup_(strbuf_cleanupp) struct strbuf *sb = NULL;
449 : 12 : _cleanup_hashmap_free_free_free_ Hashmap *h = NULL;
450 : 12 : _cleanup_free_ CatalogItem *items = NULL;
451 : : ssize_t offset;
452 : : char *payload;
453 : : CatalogItem *i;
454 : : Iterator j;
455 : : unsigned n;
456 : : int r;
457 : : int64_t sz;
458 : :
459 : 12 : h = hashmap_new(&catalog_hash_ops);
460 : 12 : sb = strbuf_new();
461 [ + - - + ]: 12 : if (!h || !sb)
462 : 0 : return log_oom();
463 : :
464 : 12 : r = conf_files_list_strv(&files, ".catalog", root, 0, dirs);
465 [ - + ]: 12 : if (r < 0)
466 [ # # ]: 0 : return log_error_errno(r, "Failed to get catalog files: %m");
467 : :
468 [ + - + + ]: 60 : STRV_FOREACH(f, files) {
469 [ + - ]: 48 : log_debug("Reading file '%s'", *f);
470 : 48 : r = catalog_import_file(h, *f);
471 [ - + ]: 48 : if (r < 0)
472 [ # # ]: 0 : return log_error_errno(r, "Failed to import file '%s': %m", *f);
473 : : }
474 : :
475 [ + + ]: 12 : if (hashmap_size(h) <= 0) {
476 [ + - ]: 8 : log_info("No items in catalog.");
477 : 8 : return 0;
478 : : } else
479 [ + - ]: 4 : log_debug("Found %u items in catalog.", hashmap_size(h));
480 : :
481 : 4 : items = new(CatalogItem, hashmap_size(h));
482 [ - + ]: 4 : if (!items)
483 : 0 : return log_oom();
484 : :
485 : 4 : n = 0;
486 [ + + ]: 1480 : HASHMAP_FOREACH_KEY(payload, i, h, j) {
487 [ + - + + ]: 1476 : log_debug("Found " SD_ID128_FORMAT_STR ", language %s",
488 : : SD_ID128_FORMAT_VAL(i->id),
489 : : isempty(i->language) ? "C" : i->language);
490 : :
491 : 1476 : offset = strbuf_add_string(sb, payload, strlen(payload));
492 [ - + ]: 1476 : if (offset < 0)
493 : 0 : return log_oom();
494 : :
495 : 1476 : i->offset = htole64((uint64_t) offset);
496 : 1476 : items[n++] = *i;
497 : : }
498 : :
499 [ - + ]: 4 : assert(n == hashmap_size(h));
500 : 4 : typesafe_qsort(items, n, catalog_compare_func);
501 : :
502 : 4 : strbuf_complete(sb);
503 : :
504 : 4 : sz = write_catalog(database, sb, items, n);
505 [ - + ]: 4 : if (sz < 0)
506 [ # # ]: 0 : return log_error_errno(sz, "Failed to write %s: %m", database);
507 : :
508 [ + - ]: 4 : log_debug("%s: wrote %u items, with %zu bytes of strings, %"PRIi64" total size.",
509 : : database, n, sb->len, sz);
510 : 4 : return 0;
511 : : }
512 : :
513 : 12 : static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
514 : 12 : _cleanup_close_ int fd = -1;
515 : : const CatalogHeader *h;
516 : : struct stat st;
517 : : void *p;
518 : :
519 [ - + ]: 12 : assert(_fd);
520 [ - + ]: 12 : assert(_st);
521 [ - + ]: 12 : assert(_p);
522 : :
523 : 12 : fd = open(database, O_RDONLY|O_CLOEXEC);
524 [ - + ]: 12 : if (fd < 0)
525 : 0 : return -errno;
526 : :
527 [ - + ]: 12 : if (fstat(fd, &st) < 0)
528 : 0 : return -errno;
529 : :
530 [ - + ]: 12 : if (st.st_size < (off_t) sizeof(CatalogHeader))
531 : 0 : return -EINVAL;
532 : :
533 : 12 : p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
534 [ - + ]: 12 : if (p == MAP_FAILED)
535 : 0 : return -errno;
536 : :
537 : 12 : h = p;
538 [ + - + - ]: 24 : if (memcmp(h->signature, (const uint8_t[]) CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
539 [ + - ]: 24 : le64toh(h->header_size) < sizeof(CatalogHeader) ||
540 : 12 : le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
541 [ + - + - ]: 24 : h->incompatible_flags != 0 ||
542 : 12 : le64toh(h->n_items) <= 0 ||
543 [ - + ]: 12 : st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) {
544 : 0 : munmap(p, st.st_size);
545 : 0 : return -EBADMSG;
546 : : }
547 : :
548 : 12 : *_fd = TAKE_FD(fd);
549 : 12 : *_st = st;
550 : 12 : *_p = p;
551 : :
552 : 12 : return 0;
553 : : }
554 : :
555 : 332 : static const char *find_id(void *p, sd_id128_t id) {
556 : 332 : CatalogItem *f = NULL, key = { .id = id };
557 : 332 : const CatalogHeader *h = p;
558 : : const char *loc;
559 : :
560 : 332 : loc = setlocale(LC_MESSAGES, NULL);
561 [ + - - + ]: 332 : if (!isempty(loc) && !STR_IN_SET(loc, "C", "POSIX")) {
562 : : size_t len;
563 : :
564 : 0 : len = strcspn(loc, ".@");
565 [ # # ]: 0 : if (len > sizeof(key.language) - 1)
566 [ # # ]: 0 : log_debug("LC_MESSAGES value too long, ignoring: \"%.*s\"", (int) len, loc);
567 : : else {
568 : 0 : strncpy(key.language, loc, len);
569 : 0 : key.language[len] = '\0';
570 : :
571 : 0 : f = bsearch(&key,
572 : 0 : (const uint8_t*) p + le64toh(h->header_size),
573 : : le64toh(h->n_items),
574 : : le64toh(h->catalog_item_size),
575 : : (comparison_fn_t) catalog_compare_func);
576 [ # # ]: 0 : if (!f) {
577 : : char *e;
578 : :
579 : 0 : e = strchr(key.language, '_');
580 [ # # ]: 0 : if (e) {
581 : 0 : *e = 0;
582 : 0 : f = bsearch(&key,
583 : 0 : (const uint8_t*) p + le64toh(h->header_size),
584 : : le64toh(h->n_items),
585 : : le64toh(h->catalog_item_size),
586 : : (comparison_fn_t) catalog_compare_func);
587 : : }
588 : : }
589 : : }
590 : : }
591 : :
592 [ + - ]: 332 : if (!f) {
593 [ + - ]: 332 : zero(key.language);
594 : 664 : f = bsearch(&key,
595 : 332 : (const uint8_t*) p + le64toh(h->header_size),
596 : : le64toh(h->n_items),
597 : : le64toh(h->catalog_item_size),
598 : : (comparison_fn_t) catalog_compare_func);
599 : : }
600 : :
601 [ - + ]: 332 : if (!f)
602 : 0 : return NULL;
603 : :
604 : : return (const char*) p +
605 : 332 : le64toh(h->header_size) +
606 : 664 : le64toh(h->n_items) * le64toh(h->catalog_item_size) +
607 : 332 : le64toh(f->offset);
608 : : }
609 : :
610 : 4 : int catalog_get(const char* database, sd_id128_t id, char **_text) {
611 : 4 : _cleanup_close_ int fd = -1;
612 : 4 : void *p = NULL;
613 : 4 : struct stat st = {};
614 : 4 : char *text = NULL;
615 : : int r;
616 : : const char *s;
617 : :
618 [ - + ]: 4 : assert(_text);
619 : :
620 : 4 : r = open_mmap(database, &fd, &st, &p);
621 [ - + ]: 4 : if (r < 0)
622 : 0 : return r;
623 : :
624 : 4 : s = find_id(p, id);
625 [ - + ]: 4 : if (!s) {
626 : 0 : r = -ENOENT;
627 : 0 : goto finish;
628 : : }
629 : :
630 : 4 : text = strdup(s);
631 [ - + ]: 4 : if (!text) {
632 : 0 : r = -ENOMEM;
633 : 0 : goto finish;
634 : : }
635 : :
636 : 4 : *_text = text;
637 : 4 : r = 0;
638 : :
639 : 4 : finish:
640 [ + - ]: 4 : if (p)
641 : 4 : munmap(p, st.st_size);
642 : :
643 : 4 : return r;
644 : : }
645 : :
646 : 492 : static char *find_header(const char *s, const char *header) {
647 : :
648 : 164 : for (;;) {
649 : : const char *v;
650 : :
651 : 492 : v = startswith(s, header);
652 [ + + ]: 492 : if (v) {
653 : 328 : v += strspn(v, WHITESPACE);
654 : 328 : return strndup(v, strcspn(v, NEWLINE));
655 : : }
656 : :
657 [ - + ]: 164 : if (!next_header(&s))
658 : 0 : return NULL;
659 : : }
660 : : }
661 : :
662 : 328 : static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
663 [ + + ]: 328 : if (oneline) {
664 : 164 : _cleanup_free_ char *subject = NULL, *defined_by = NULL;
665 : :
666 : 164 : subject = find_header(s, "Subject:");
667 : 164 : defined_by = find_header(s, "Defined-By:");
668 : :
669 : 164 : fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
670 : 164 : SD_ID128_FORMAT_VAL(id),
671 : : strna(defined_by), strna(subject));
672 : : } else
673 : 2624 : fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
674 : 164 : SD_ID128_FORMAT_VAL(id), s);
675 : 328 : }
676 : :
677 : 8 : int catalog_list(FILE *f, const char *database, bool oneline) {
678 : 8 : _cleanup_close_ int fd = -1;
679 : 8 : void *p = NULL;
680 : : struct stat st;
681 : : const CatalogHeader *h;
682 : : const CatalogItem *items;
683 : : int r;
684 : : unsigned n;
685 : : sd_id128_t last_id;
686 : 8 : bool last_id_set = false;
687 : :
688 : 8 : r = open_mmap(database, &fd, &st, &p);
689 [ - + ]: 8 : if (r < 0)
690 : 0 : return r;
691 : :
692 : 8 : h = p;
693 : 8 : items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
694 : :
695 [ + + ]: 2960 : for (n = 0; n < le64toh(h->n_items); n++) {
696 : : const char *s;
697 : :
698 [ + + + + ]: 2952 : if (last_id_set && sd_id128_equal(last_id, items[n].id))
699 : 2624 : continue;
700 : :
701 [ - + ]: 328 : assert_se(s = find_id(p, items[n].id));
702 : :
703 : 328 : dump_catalog_entry(f, items[n].id, s, oneline);
704 : :
705 : 328 : last_id_set = true;
706 : 328 : last_id = items[n].id;
707 : : }
708 : :
709 : 8 : munmap(p, st.st_size);
710 : :
711 : 8 : return 0;
712 : : }
713 : :
714 : 0 : int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) {
715 : : char **item;
716 : 0 : int r = 0;
717 : :
718 [ # # # # ]: 0 : STRV_FOREACH(item, items) {
719 : : sd_id128_t id;
720 : : int k;
721 [ # # ]: 0 : _cleanup_free_ char *msg = NULL;
722 : :
723 : 0 : k = sd_id128_from_string(*item, &id);
724 [ # # ]: 0 : if (k < 0) {
725 [ # # ]: 0 : log_error_errno(k, "Failed to parse id128 '%s': %m", *item);
726 [ # # ]: 0 : if (r == 0)
727 : 0 : r = k;
728 : 0 : continue;
729 : : }
730 : :
731 : 0 : k = catalog_get(database, id, &msg);
732 [ # # ]: 0 : if (k < 0) {
733 [ # # # # ]: 0 : log_full_errno(k == -ENOENT ? LOG_NOTICE : LOG_ERR, k,
734 : : "Failed to retrieve catalog entry for '%s': %m", *item);
735 [ # # ]: 0 : if (r == 0)
736 : 0 : r = k;
737 : 0 : continue;
738 : : }
739 : :
740 : 0 : dump_catalog_entry(f, id, msg, oneline);
741 : : }
742 : :
743 : 0 : return r;
744 : : }
|