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