1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
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.
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.
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/>.
33 #include "sparse-endian.h"
39 #include "conf-files.h"
42 #include "siphash24.h"
44 const char * const catalog_file_dirs
[] = {
45 "/usr/local/lib/systemd/catalog/",
46 "/usr/lib/systemd/catalog/",
50 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
52 typedef struct CatalogHeader
{
53 uint8_t signature
[8]; /* "RHHHKSLP" */
54 le32_t compatible_flags
;
55 le32_t incompatible_flags
;
58 le64_t catalog_item_size
;
61 typedef struct CatalogItem
{
67 static unsigned long catalog_hash_func(const void *p
, const uint8_t hash_key
[HASH_KEY_SIZE
]) {
68 const CatalogItem
*i
= p
;
73 l
= strlen(i
->language
);
74 sz
= sizeof(i
->id
) + l
;
77 memcpy(mempcpy(v
, &i
->id
, sizeof(i
->id
)), i
->language
, l
);
79 siphash24((uint8_t*) &u
, v
, sz
, hash_key
);
81 return (unsigned long) u
;
84 static int catalog_compare_func(const void *a
, const void *b
) {
85 const CatalogItem
*i
= a
, *j
= b
;
88 for (k
= 0; k
< ELEMENTSOF(j
->id
.bytes
); k
++) {
89 if (i
->id
.bytes
[k
] < j
->id
.bytes
[k
])
91 if (i
->id
.bytes
[k
] > j
->id
.bytes
[k
])
95 return strcmp(i
->language
, j
->language
);
98 const struct hash_ops catalog_hash_ops
= {
99 .hash
= catalog_hash_func
,
100 .compare
= catalog_compare_func
103 static int finish_item(
107 const char *language
,
108 const char *payload
) {
111 _cleanup_free_ CatalogItem
*i
= NULL
;
118 offset
= strbuf_add_string(sb
, payload
, strlen(payload
));
122 i
= new0(CatalogItem
, 1);
128 assert(strlen(language
) > 1 && strlen(language
) < 32);
129 strcpy(i
->language
, language
);
131 i
->offset
= htole64((uint64_t) offset
);
133 r
= hashmap_put(h
, i
, i
);
135 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR
".%s, ignoring.",
136 SD_ID128_FORMAT_VAL(id
), language
? language
: "C");
145 int catalog_file_lang(const char* filename
, char **lang
) {
146 char *beg
, *end
, *_lang
;
148 end
= endswith(filename
, ".catalog");
153 while (beg
> filename
&& *beg
!= '.' && *beg
!= '/' && end
- beg
< 32)
156 if (*beg
!= '.' || end
<= beg
+ 1)
159 _lang
= strndup(beg
+ 1, end
- beg
- 1);
167 static int catalog_entry_lang(const char* filename
, int line
,
168 const char* t
, const char* deflang
, char **lang
) {
173 log_error("[%s:%u] Language too short.", filename
, line
);
177 log_error("[%s:%u] language too long.", filename
, line
);
182 if (streq(t
, deflang
)) {
183 log_warning("[%s:%u] language specified unnecessarily",
187 log_warning("[%s:%u] language differs from default for file",
198 int catalog_import_file(Hashmap
*h
, struct strbuf
*sb
, const char *path
) {
199 _cleanup_fclose_
FILE *f
= NULL
;
200 _cleanup_free_
char *payload
= NULL
;
203 _cleanup_free_
char *deflang
= NULL
, *lang
= NULL
;
204 bool got_id
= false, empty_line
= true;
211 f
= fopen(path
, "re");
213 return log_error_errno(errno
, "Failed to open file %s: %m", path
);
215 r
= catalog_file_lang(path
, &deflang
);
217 log_error_errno(errno
, "Failed to determine language for file %s: %m", path
);
219 log_debug("File %s has language %s.", path
, deflang
);
226 if (!fgets(line
, sizeof(line
), f
)) {
230 log_error_errno(errno
, "Failed to read file %s: %m", path
);
243 if (strchr(COMMENTS
"\n", line
[0]))
247 strlen(line
) >= 2+1+32 &&
251 (line
[2+1+32] == ' ' || line
[2+1+32] == '\0')) {
258 with_language
= line
[2+1+32] != '\0';
261 if (sd_id128_from_string(line
+ 2 + 1, &jd
) >= 0) {
264 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
273 t
= strstrip(line
+ 2 + 1 + 32 + 1);
275 r
= catalog_entry_lang(path
, n
, t
, deflang
, &lang
);
293 log_error("[%s:%u] Got payload before ID.", path
, n
);
297 a
= payload
? strlen(payload
) : 0;
300 c
= a
+ (empty_line
? 1 : 0) + b
+ 1 + 1;
301 t
= realloc(payload
, c
);
307 memcpy(t
+ a
+ 1, line
, b
);
311 memcpy(t
+ a
, line
, b
);
321 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
329 static long write_catalog(const char *database
, Hashmap
*h
, struct strbuf
*sb
,
330 CatalogItem
*items
, size_t n
) {
331 CatalogHeader header
;
332 _cleanup_fclose_
FILE *w
= NULL
;
334 _cleanup_free_
char *d
, *p
= NULL
;
337 d
= dirname_malloc(database
);
341 r
= mkdir_p(d
, 0775);
343 return log_error_errno(r
, "Recursive mkdir %s: %m", d
);
345 r
= fopen_temporary(database
, &w
, &p
);
347 return log_error_errno(r
, "Failed to open database for writing: %s: %m",
351 memcpy(header
.signature
, CATALOG_SIGNATURE
, sizeof(header
.signature
));
352 header
.header_size
= htole64(ALIGN_TO(sizeof(CatalogHeader
), 8));
353 header
.catalog_item_size
= htole64(sizeof(CatalogItem
));
354 header
.n_items
= htole64(hashmap_size(h
));
358 k
= fwrite(&header
, 1, sizeof(header
), w
);
359 if (k
!= sizeof(header
)) {
360 log_error("%s: failed to write header.", p
);
364 k
= fwrite(items
, 1, n
* sizeof(CatalogItem
), w
);
365 if (k
!= n
* sizeof(CatalogItem
)) {
366 log_error("%s: failed to write database.", p
);
370 k
= fwrite(sb
->buf
, 1, sb
->len
, w
);
372 log_error("%s: failed to write strings.", p
);
379 log_error("%s: failed to write database.", p
);
383 fchmod(fileno(w
), 0644);
385 if (rename(p
, database
) < 0) {
386 log_error_errno(errno
, "rename (%s -> %s) failed: %m", p
, database
);
398 int catalog_update(const char* database
, const char* root
, const char* const* dirs
) {
399 _cleanup_strv_free_
char **files
= NULL
;
401 struct strbuf
*sb
= NULL
;
402 _cleanup_hashmap_free_free_ Hashmap
*h
= NULL
;
403 _cleanup_free_ CatalogItem
*items
= NULL
;
409 h
= hashmap_new(&catalog_hash_ops
);
417 r
= conf_files_list_strv(&files
, ".catalog", root
, dirs
);
419 log_error_errno(r
, "Failed to get catalog files: %m");
423 STRV_FOREACH(f
, files
) {
424 log_debug("Reading file '%s'", *f
);
425 r
= catalog_import_file(h
, sb
, *f
);
427 log_error("Failed to import file '%s': %s.",
433 if (hashmap_size(h
) <= 0) {
434 log_info("No items in catalog.");
437 log_debug("Found %u items in catalog.", hashmap_size(h
));
441 items
= new(CatalogItem
, hashmap_size(h
));
448 HASHMAP_FOREACH(i
, h
, j
) {
449 log_debug("Found " SD_ID128_FORMAT_STR
", language %s",
450 SD_ID128_FORMAT_VAL(i
->id
),
451 isempty(i
->language
) ? "C" : i
->language
);
455 assert(n
== hashmap_size(h
));
456 qsort_safe(items
, n
, sizeof(CatalogItem
), catalog_compare_func
);
458 r
= write_catalog(database
, h
, sb
, items
, n
);
460 log_error_errno(r
, "Failed to write %s: %m", database
);
462 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
463 database
, n
, sb
->len
, r
);
469 return r
< 0 ? r
: 0;
472 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
473 const CatalogHeader
*h
;
482 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
486 if (fstat(fd
, &st
) < 0) {
491 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
496 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
497 if (p
== MAP_FAILED
) {
503 if (memcmp(h
->signature
, CATALOG_SIGNATURE
, sizeof(h
->signature
)) != 0 ||
504 le64toh(h
->header_size
) < sizeof(CatalogHeader
) ||
505 le64toh(h
->catalog_item_size
) < sizeof(CatalogItem
) ||
506 h
->incompatible_flags
!= 0 ||
507 le64toh(h
->n_items
) <= 0 ||
508 st
.st_size
< (off_t
) (le64toh(h
->header_size
) + le64toh(h
->catalog_item_size
) * le64toh(h
->n_items
))) {
510 munmap(p
, st
.st_size
);
521 static const char *find_id(void *p
, sd_id128_t id
) {
522 CatalogItem key
, *f
= NULL
;
523 const CatalogHeader
*h
= p
;
529 loc
= setlocale(LC_MESSAGES
, NULL
);
530 if (loc
&& loc
[0] && !streq(loc
, "C") && !streq(loc
, "POSIX")) {
531 strncpy(key
.language
, loc
, sizeof(key
.language
));
532 key
.language
[strcspn(key
.language
, ".@")] = 0;
534 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
538 e
= strchr(key
.language
, '_');
541 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
548 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
554 return (const char*) p
+
555 le64toh(h
->header_size
) +
556 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
560 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
561 _cleanup_close_
int fd
= -1;
570 r
= open_mmap(database
, &fd
, &st
, &p
);
591 munmap(p
, st
.st_size
);
596 static char *find_header(const char *s
, const char *header
) {
601 v
= startswith(s
, header
);
603 v
+= strspn(v
, WHITESPACE
);
604 return strndup(v
, strcspn(v
, NEWLINE
));
620 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
622 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
624 subject
= find_header(s
, "Subject:");
625 defined_by
= find_header(s
, "Defined-By:");
627 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
628 SD_ID128_FORMAT_VAL(id
),
629 strna(defined_by
), strna(subject
));
631 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
632 SD_ID128_FORMAT_VAL(id
), s
);
636 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
637 _cleanup_close_
int fd
= -1;
640 const CatalogHeader
*h
;
641 const CatalogItem
*items
;
645 bool last_id_set
= false;
647 r
= open_mmap(database
, &fd
, &st
, &p
);
652 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
654 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
657 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
660 assert_se(s
= find_id(p
, items
[n
].id
));
662 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
665 last_id
= items
[n
].id
;
668 munmap(p
, st
.st_size
);
673 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
677 STRV_FOREACH(item
, items
) {
680 _cleanup_free_
char *msg
= NULL
;
682 k
= sd_id128_from_string(*item
, &id
);
684 log_error_errno(k
, "Failed to parse id128 '%s': %m",
691 k
= catalog_get(database
, id
, &msg
);
693 log_full(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
,
694 "Failed to retrieve catalog entry for '%s': %s",
695 *item
, strerror(-k
));
701 dump_catalog_entry(f
, id
, msg
, oneline
);