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 1135 : static void catalog_hash_func(const CatalogItem *i, struct siphash *state) {
58 1135 : siphash24_compress(&i->id, sizeof(i->id), state);
59 1135 : siphash24_compress(i->language, strlen(i->language), state);
60 1135 : }
61 :
62 3770 : static int catalog_compare_func(const CatalogItem *a, const CatalogItem *b) {
63 : unsigned k;
64 : int r;
65 :
66 22027 : for (k = 0; k < ELEMENTSOF(b->id.bytes); k++) {
67 20889 : r = CMP(a->id.bytes[k], b->id.bytes[k]);
68 20889 : if (r != 0)
69 2632 : return r;
70 : }
71 :
72 1138 : return strcmp(a->language, b->language);
73 : }
74 :
75 : DEFINE_HASH_OPS(catalog_hash_ops, CatalogItem, catalog_hash_func, catalog_compare_func);
76 :
77 53 : static bool next_header(const char **s) {
78 : const char *e;
79 :
80 53 : e = strchr(*s, '\n');
81 :
82 : /* Unexpected end */
83 53 : if (!e)
84 1 : return false;
85 :
86 : /* End of headers */
87 52 : if (e == *s)
88 3 : return false;
89 :
90 49 : *s = e + 1;
91 49 : return true;
92 : }
93 :
94 4 : static const char *skip_header(const char *s) {
95 12 : while (next_header(&s))
96 : ;
97 4 : return s;
98 : }
99 :
100 2 : 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 2 : b1 = skip_header(one);
107 2 : b2 = skip_header(two);
108 :
109 2 : l1 = strlen(one);
110 2 : l2 = strlen(two);
111 2 : dest = new(char, l1 + l2 + 1);
112 2 : if (!dest) {
113 0 : log_oom();
114 0 : return NULL;
115 : }
116 :
117 2 : p = dest;
118 :
119 : /* Headers from @one */
120 2 : n = b1 - one;
121 2 : p = mempcpy(p, one, n);
122 :
123 : /* Headers from @two, these will only be found if not present above */
124 2 : n = b2 - two;
125 2 : p = mempcpy(p, two, n);
126 :
127 : /* Body from @one */
128 2 : n = l1 - (b1 - one);
129 2 : if (n > 0) {
130 1 : memcpy(p, b1, n);
131 1 : p += n;
132 :
133 : /* Body from @two */
134 : } else {
135 1 : n = l2 - (b2 - two);
136 1 : memcpy(p, b2, n);
137 1 : p += n;
138 : }
139 :
140 2 : assert(p - dest <= (ptrdiff_t)(l1 + l2));
141 2 : p[0] = '\0';
142 2 : return dest;
143 : }
144 :
145 374 : 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 374 : _cleanup_free_ CatalogItem *i = NULL;
152 374 : _cleanup_free_ char *prev = NULL, *combined = NULL;
153 :
154 374 : assert(h);
155 374 : assert(payload);
156 374 : assert(payload_size > 0);
157 :
158 374 : i = new0(CatalogItem, 1);
159 374 : if (!i)
160 0 : return log_oom();
161 :
162 374 : i->id = id;
163 374 : if (language) {
164 333 : assert(strlen(language) > 1 && strlen(language) < 32);
165 333 : strcpy(i->language, language);
166 : }
167 :
168 374 : prev = hashmap_get(h, i);
169 374 : if (prev) {
170 : /* Already have such an item, combine them */
171 2 : combined = combine_entries(payload, prev);
172 2 : if (!combined)
173 0 : return log_oom();
174 :
175 2 : if (hashmap_update(h, i, combined) < 0)
176 0 : return log_oom();
177 2 : combined = NULL;
178 : } else {
179 : /* A new item */
180 372 : combined = memdup(payload, payload_size + 1);
181 372 : if (!combined)
182 0 : return log_oom();
183 :
184 372 : if (hashmap_put(h, i, combined) < 0)
185 0 : return log_oom();
186 372 : i = NULL;
187 372 : combined = NULL;
188 : }
189 :
190 374 : return 0;
191 : }
192 :
193 25 : int catalog_file_lang(const char* filename, char **lang) {
194 : char *beg, *end, *_lang;
195 :
196 25 : end = endswith(filename, ".catalog");
197 25 : if (!end)
198 6 : return 0;
199 :
200 19 : beg = end - 1;
201 144 : while (beg > filename && !IN_SET(*beg, '.', '/') && end - beg < 32)
202 125 : beg--;
203 :
204 19 : if (*beg != '.' || end <= beg + 1)
205 4 : return 0;
206 :
207 15 : _lang = strndup(beg + 1, end - beg - 1);
208 15 : if (!_lang)
209 0 : return -ENOMEM;
210 :
211 15 : *lang = _lang;
212 15 : return 1;
213 : }
214 :
215 7 : 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 7 : c = strlen(t);
226 7 : if (c < 2)
227 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
228 : "[%s:%u] Language too short.", filename, line);
229 7 : if (c > 31)
230 1 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
231 : "[%s:%u] language too long.", filename, line);
232 :
233 6 : 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 6 : z = strdup(t);
243 6 : if (!z)
244 0 : return -ENOMEM;
245 :
246 6 : *ret = z;
247 6 : return 0;
248 : }
249 :
250 17 : int catalog_import_file(Hashmap *h, const char *path) {
251 17 : _cleanup_fclose_ FILE *f = NULL;
252 17 : _cleanup_free_ char *payload = NULL;
253 17 : size_t payload_size = 0, payload_allocated = 0;
254 17 : unsigned n = 0;
255 : sd_id128_t id;
256 17 : _cleanup_free_ char *deflang = NULL, *lang = NULL;
257 17 : bool got_id = false, empty_line = true;
258 : int r;
259 :
260 17 : assert(h);
261 17 : assert(path);
262 :
263 17 : f = fopen(path, "re");
264 17 : if (!f)
265 0 : return log_error_errno(errno, "Failed to open file %s: %m", path);
266 :
267 17 : r = catalog_file_lang(path, &deflang);
268 17 : if (r < 0)
269 0 : log_error_errno(r, "Failed to determine language for file %s: %m", path);
270 17 : if (r == 1)
271 11 : log_debug("File %s has language %s.", path, deflang);
272 :
273 3692 : for (;;) {
274 3709 : _cleanup_free_ char *line = NULL;
275 : size_t line_len;
276 :
277 3709 : r = read_line(f, LONG_LINE_MAX, &line);
278 3709 : if (r < 0)
279 0 : return log_error_errno(r, "Failed to read file %s: %m", path);
280 3709 : if (r == 0)
281 15 : break;
282 :
283 3694 : n++;
284 :
285 3694 : if (isempty(line)) {
286 944 : empty_line = true;
287 944 : continue;
288 : }
289 :
290 2750 : if (strchr(COMMENTS, line[0]))
291 156 : continue;
292 :
293 2594 : if (empty_line &&
294 919 : strlen(line) >= 2+1+32 &&
295 870 : line[0] == '-' &&
296 375 : line[1] == '-' &&
297 375 : line[2] == ' ' &&
298 375 : IN_SET(line[2+1+32], ' ', '\0')) {
299 :
300 : bool with_language;
301 : sd_id128_t jd;
302 :
303 : /* New entry */
304 :
305 375 : with_language = line[2+1+32] != '\0';
306 375 : line[2+1+32] = '\0';
307 :
308 375 : if (sd_id128_from_string(line + 2 + 1, &jd) >= 0) {
309 :
310 375 : if (got_id) {
311 359 : if (payload_size == 0)
312 1 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
313 : "[%s:%u] No payload text.",
314 : path,
315 : n);
316 :
317 359 : r = finish_item(h, id, lang ?: deflang, payload, payload_size);
318 359 : if (r < 0)
319 0 : return r;
320 :
321 359 : lang = mfree(lang);
322 359 : payload_size = 0;
323 : }
324 :
325 375 : if (with_language) {
326 : char *t;
327 :
328 7 : t = strstrip(line + 2 + 1 + 32 + 1);
329 7 : r = catalog_entry_lang(path, n, t, deflang, &lang);
330 7 : if (r < 0)
331 1 : return r;
332 : }
333 :
334 374 : got_id = true;
335 374 : empty_line = false;
336 374 : id = jd;
337 :
338 374 : continue;
339 : }
340 : }
341 :
342 : /* Payload */
343 2219 : if (!got_id)
344 1 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
345 : "[%s:%u] Got payload before ID.",
346 : path, n);
347 :
348 2218 : line_len = strlen(line);
349 2218 : if (!GREEDY_REALLOC(payload, payload_allocated,
350 : payload_size + (empty_line ? 1 : 0) + line_len + 1 + 1))
351 0 : return log_oom();
352 :
353 2218 : if (empty_line)
354 543 : payload[payload_size++] = '\n';
355 2218 : memcpy(payload + payload_size, line, line_len);
356 2218 : payload_size += line_len;
357 2218 : payload[payload_size++] = '\n';
358 2218 : payload[payload_size] = '\0';
359 :
360 2218 : empty_line = false;
361 : }
362 :
363 15 : if (got_id) {
364 15 : if (payload_size == 0)
365 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
366 : "[%s:%u] No payload text.",
367 : path, n);
368 :
369 15 : r = finish_item(h, id, lang ?: deflang, payload, payload_size);
370 15 : if (r < 0)
371 0 : return r;
372 : }
373 :
374 15 : return 0;
375 : }
376 :
377 1 : static int64_t write_catalog(
378 : const char *database,
379 : struct strbuf *sb,
380 : CatalogItem *items,
381 : size_t n) {
382 :
383 1 : _cleanup_fclose_ FILE *w = NULL;
384 1 : _cleanup_free_ char *p = NULL;
385 : CatalogHeader header;
386 : size_t k;
387 : int r;
388 :
389 1 : r = mkdir_parents(database, 0755);
390 1 : if (r < 0)
391 0 : return log_error_errno(r, "Failed to create parent directories of %s: %m", database);
392 :
393 1 : r = fopen_temporary(database, &w, &p);
394 1 : if (r < 0)
395 0 : return log_error_errno(r, "Failed to open database for writing: %s: %m",
396 : database);
397 :
398 1 : header = (CatalogHeader) {
399 : .signature = CATALOG_SIGNATURE,
400 1 : .header_size = htole64(ALIGN_TO(sizeof(CatalogHeader), 8)),
401 1 : .catalog_item_size = htole64(sizeof(CatalogItem)),
402 1 : .n_items = htole64(n),
403 : };
404 :
405 1 : r = -EIO;
406 :
407 1 : k = fwrite(&header, 1, sizeof(header), w);
408 1 : if (k != sizeof(header)) {
409 0 : log_error("%s: failed to write header.", p);
410 0 : goto error;
411 : }
412 :
413 1 : k = fwrite(items, 1, n * sizeof(CatalogItem), w);
414 1 : if (k != n * sizeof(CatalogItem)) {
415 0 : log_error("%s: failed to write database.", p);
416 0 : goto error;
417 : }
418 :
419 1 : k = fwrite(sb->buf, 1, sb->len, w);
420 1 : if (k != sb->len) {
421 0 : log_error("%s: failed to write strings.", p);
422 0 : goto error;
423 : }
424 :
425 1 : r = fflush_and_check(w);
426 1 : if (r < 0) {
427 0 : log_error_errno(r, "%s: failed to write database: %m", p);
428 0 : goto error;
429 : }
430 :
431 1 : (void) fchmod(fileno(w), 0644);
432 :
433 1 : 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 1 : return ftello(w);
439 :
440 0 : error:
441 0 : (void) unlink(p);
442 0 : return r;
443 : }
444 :
445 3 : int catalog_update(const char* database, const char* root, const char* const* dirs) {
446 3 : _cleanup_strv_free_ char **files = NULL;
447 : char **f;
448 3 : _cleanup_(strbuf_cleanupp) struct strbuf *sb = NULL;
449 3 : _cleanup_hashmap_free_free_free_ Hashmap *h = NULL;
450 3 : _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 3 : h = hashmap_new(&catalog_hash_ops);
460 3 : sb = strbuf_new();
461 3 : if (!h || !sb)
462 0 : return log_oom();
463 :
464 3 : r = conf_files_list_strv(&files, ".catalog", root, 0, dirs);
465 3 : if (r < 0)
466 0 : return log_error_errno(r, "Failed to get catalog files: %m");
467 :
468 15 : STRV_FOREACH(f, files) {
469 12 : log_debug("Reading file '%s'", *f);
470 12 : r = catalog_import_file(h, *f);
471 12 : if (r < 0)
472 0 : return log_error_errno(r, "Failed to import file '%s': %m", *f);
473 : }
474 :
475 3 : if (hashmap_size(h) <= 0) {
476 2 : log_info("No items in catalog.");
477 2 : return 0;
478 : } else
479 1 : log_debug("Found %u items in catalog.", hashmap_size(h));
480 :
481 1 : items = new(CatalogItem, hashmap_size(h));
482 1 : if (!items)
483 0 : return log_oom();
484 :
485 1 : n = 0;
486 370 : HASHMAP_FOREACH_KEY(payload, i, h, j) {
487 369 : 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 369 : offset = strbuf_add_string(sb, payload, strlen(payload));
492 369 : if (offset < 0)
493 0 : return log_oom();
494 :
495 369 : i->offset = htole64((uint64_t) offset);
496 369 : items[n++] = *i;
497 : }
498 :
499 1 : assert(n == hashmap_size(h));
500 1 : typesafe_qsort(items, n, catalog_compare_func);
501 :
502 1 : strbuf_complete(sb);
503 :
504 1 : sz = write_catalog(database, sb, items, n);
505 1 : if (sz < 0)
506 0 : return log_error_errno(sz, "Failed to write %s: %m", database);
507 :
508 1 : log_debug("%s: wrote %u items, with %zu bytes of strings, %"PRIi64" total size.",
509 : database, n, sb->len, sz);
510 1 : return 0;
511 : }
512 :
513 3 : static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) {
514 3 : _cleanup_close_ int fd = -1;
515 : const CatalogHeader *h;
516 : struct stat st;
517 : void *p;
518 :
519 3 : assert(_fd);
520 3 : assert(_st);
521 3 : assert(_p);
522 :
523 3 : fd = open(database, O_RDONLY|O_CLOEXEC);
524 3 : if (fd < 0)
525 0 : return -errno;
526 :
527 3 : if (fstat(fd, &st) < 0)
528 0 : return -errno;
529 :
530 3 : if (st.st_size < (off_t) sizeof(CatalogHeader))
531 0 : return -EINVAL;
532 :
533 3 : p = mmap(NULL, PAGE_ALIGN(st.st_size), PROT_READ, MAP_SHARED, fd, 0);
534 3 : if (p == MAP_FAILED)
535 0 : return -errno;
536 :
537 3 : h = p;
538 6 : if (memcmp(h->signature, (const uint8_t[]) CATALOG_SIGNATURE, sizeof(h->signature)) != 0 ||
539 6 : le64toh(h->header_size) < sizeof(CatalogHeader) ||
540 3 : le64toh(h->catalog_item_size) < sizeof(CatalogItem) ||
541 6 : h->incompatible_flags != 0 ||
542 3 : le64toh(h->n_items) <= 0 ||
543 3 : 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 3 : *_fd = TAKE_FD(fd);
549 3 : *_st = st;
550 3 : *_p = p;
551 :
552 3 : return 0;
553 : }
554 :
555 83 : static const char *find_id(void *p, sd_id128_t id) {
556 83 : CatalogItem *f = NULL, key = { .id = id };
557 83 : const CatalogHeader *h = p;
558 : const char *loc;
559 :
560 83 : loc = setlocale(LC_MESSAGES, NULL);
561 83 : 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 83 : if (!f) {
593 83 : zero(key.language);
594 166 : f = bsearch(&key,
595 83 : (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 83 : if (!f)
602 0 : return NULL;
603 :
604 : return (const char*) p +
605 83 : le64toh(h->header_size) +
606 166 : le64toh(h->n_items) * le64toh(h->catalog_item_size) +
607 83 : le64toh(f->offset);
608 : }
609 :
610 1 : int catalog_get(const char* database, sd_id128_t id, char **_text) {
611 1 : _cleanup_close_ int fd = -1;
612 1 : void *p = NULL;
613 1 : struct stat st = {};
614 1 : char *text = NULL;
615 : int r;
616 : const char *s;
617 :
618 1 : assert(_text);
619 :
620 1 : r = open_mmap(database, &fd, &st, &p);
621 1 : if (r < 0)
622 0 : return r;
623 :
624 1 : s = find_id(p, id);
625 1 : if (!s) {
626 0 : r = -ENOENT;
627 0 : goto finish;
628 : }
629 :
630 1 : text = strdup(s);
631 1 : if (!text) {
632 0 : r = -ENOMEM;
633 0 : goto finish;
634 : }
635 :
636 1 : *_text = text;
637 1 : r = 0;
638 :
639 1 : finish:
640 1 : if (p)
641 1 : munmap(p, st.st_size);
642 :
643 1 : return r;
644 : }
645 :
646 123 : static char *find_header(const char *s, const char *header) {
647 :
648 41 : for (;;) {
649 : const char *v;
650 :
651 123 : v = startswith(s, header);
652 123 : if (v) {
653 82 : v += strspn(v, WHITESPACE);
654 82 : return strndup(v, strcspn(v, NEWLINE));
655 : }
656 :
657 41 : if (!next_header(&s))
658 0 : return NULL;
659 : }
660 : }
661 :
662 82 : static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) {
663 82 : if (oneline) {
664 41 : _cleanup_free_ char *subject = NULL, *defined_by = NULL;
665 :
666 41 : subject = find_header(s, "Subject:");
667 41 : defined_by = find_header(s, "Defined-By:");
668 :
669 41 : fprintf(f, SD_ID128_FORMAT_STR " %s: %s\n",
670 41 : SD_ID128_FORMAT_VAL(id),
671 : strna(defined_by), strna(subject));
672 : } else
673 656 : fprintf(f, "-- " SD_ID128_FORMAT_STR "\n%s\n",
674 41 : SD_ID128_FORMAT_VAL(id), s);
675 82 : }
676 :
677 2 : int catalog_list(FILE *f, const char *database, bool oneline) {
678 2 : _cleanup_close_ int fd = -1;
679 2 : 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 2 : bool last_id_set = false;
687 :
688 2 : r = open_mmap(database, &fd, &st, &p);
689 2 : if (r < 0)
690 0 : return r;
691 :
692 2 : h = p;
693 2 : items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size));
694 :
695 740 : for (n = 0; n < le64toh(h->n_items); n++) {
696 : const char *s;
697 :
698 738 : if (last_id_set && sd_id128_equal(last_id, items[n].id))
699 656 : continue;
700 :
701 82 : assert_se(s = find_id(p, items[n].id));
702 :
703 82 : dump_catalog_entry(f, items[n].id, s, oneline);
704 :
705 82 : last_id_set = true;
706 82 : last_id = items[n].id;
707 : }
708 :
709 2 : munmap(p, st.st_size);
710 :
711 2 : 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 : }
|