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/>.
32 #include "sparse-endian.h"
38 #include "conf-files.h"
41 #include "siphash24.h"
43 const char * const catalog_file_dirs
[] = {
44 "/usr/local/lib/systemd/catalog/",
45 "/usr/lib/systemd/catalog/",
49 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
51 typedef struct CatalogHeader
{
52 uint8_t signature
[8]; /* "RHHHKSLP" */
53 le32_t compatible_flags
;
54 le32_t incompatible_flags
;
57 le64_t catalog_item_size
;
60 typedef struct CatalogItem
{
66 static unsigned long catalog_hash_func(const void *p
, const uint8_t hash_key
[HASH_KEY_SIZE
]) {
67 const CatalogItem
*i
= p
;
72 l
= strlen(i
->language
);
73 sz
= sizeof(i
->id
) + l
;
76 memcpy(mempcpy(v
, &i
->id
, sizeof(i
->id
)), i
->language
, l
);
78 siphash24((uint8_t*) &u
, v
, sz
, hash_key
);
80 return (unsigned long) u
;
83 static int catalog_compare_func(const void *a
, const void *b
) {
84 const CatalogItem
*i
= a
, *j
= b
;
87 for (k
= 0; k
< ELEMENTSOF(j
->id
.bytes
); k
++) {
88 if (i
->id
.bytes
[k
] < j
->id
.bytes
[k
])
90 if (i
->id
.bytes
[k
] > j
->id
.bytes
[k
])
94 return strcmp(i
->language
, j
->language
);
97 const struct hash_ops catalog_hash_ops
= {
98 .hash
= catalog_hash_func
,
99 .compare
= catalog_compare_func
102 static int finish_item(
106 const char *language
,
107 const char *payload
) {
110 _cleanup_free_ CatalogItem
*i
= NULL
;
117 offset
= strbuf_add_string(sb
, payload
, strlen(payload
));
121 i
= new0(CatalogItem
, 1);
127 assert(strlen(language
) > 1 && strlen(language
) < 32);
128 strcpy(i
->language
, language
);
130 i
->offset
= htole64((uint64_t) offset
);
132 r
= hashmap_put(h
, i
, i
);
134 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR
".%s, ignoring.",
135 SD_ID128_FORMAT_VAL(id
), language
? language
: "C");
144 int catalog_file_lang(const char* filename
, char **lang
) {
145 char *beg
, *end
, *_lang
;
147 end
= endswith(filename
, ".catalog");
152 while (beg
> filename
&& *beg
!= '.' && *beg
!= '/' && end
- beg
< 32)
155 if (*beg
!= '.' || end
<= beg
+ 1)
158 _lang
= strndup(beg
+ 1, end
- beg
- 1);
166 static int catalog_entry_lang(const char* filename
, int line
,
167 const char* t
, const char* deflang
, char **lang
) {
172 log_error("[%s:%u] Language too short.", filename
, line
);
176 log_error("[%s:%u] language too long.", filename
, line
);
181 if (streq(t
, deflang
)) {
182 log_warning("[%s:%u] language specified unnecessarily",
186 log_warning("[%s:%u] language differs from default for file",
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
;
202 _cleanup_free_
char *deflang
= NULL
, *lang
= NULL
;
203 bool got_id
= false, empty_line
= true;
210 f
= fopen(path
, "re");
212 return log_error_errno(errno
, "Failed to open file %s: %m", path
);
214 r
= catalog_file_lang(path
, &deflang
);
216 log_error_errno(errno
, "Failed to determine language for file %s: %m", path
);
218 log_debug("File %s has language %s.", path
, deflang
);
225 if (!fgets(line
, sizeof(line
), f
)) {
229 log_error_errno(errno
, "Failed to read file %s: %m", path
);
242 if (strchr(COMMENTS
"\n", line
[0]))
246 strlen(line
) >= 2+1+32 &&
250 (line
[2+1+32] == ' ' || line
[2+1+32] == '\0')) {
257 with_language
= line
[2+1+32] != '\0';
260 if (sd_id128_from_string(line
+ 2 + 1, &jd
) >= 0) {
263 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
272 t
= strstrip(line
+ 2 + 1 + 32 + 1);
274 r
= catalog_entry_lang(path
, n
, t
, deflang
, &lang
);
292 log_error("[%s:%u] Got payload before ID.", path
, n
);
296 a
= payload
? strlen(payload
) : 0;
299 c
= a
+ (empty_line
? 1 : 0) + b
+ 1 + 1;
300 t
= realloc(payload
, c
);
306 memcpy(t
+ a
+ 1, line
, b
);
310 memcpy(t
+ a
, line
, b
);
320 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
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
;
333 _cleanup_free_
char *d
, *p
= NULL
;
336 d
= dirname_malloc(database
);
340 r
= mkdir_p(d
, 0775);
342 return log_error_errno(r
, "Recursive mkdir %s: %m", d
);
344 r
= fopen_temporary(database
, &w
, &p
);
346 return log_error_errno(r
, "Failed to open database for writing: %s: %m",
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
));
357 k
= fwrite(&header
, 1, sizeof(header
), w
);
358 if (k
!= sizeof(header
)) {
359 log_error("%s: failed to write header.", p
);
363 k
= fwrite(items
, 1, n
* sizeof(CatalogItem
), w
);
364 if (k
!= n
* sizeof(CatalogItem
)) {
365 log_error("%s: failed to write database.", p
);
369 k
= fwrite(sb
->buf
, 1, sb
->len
, w
);
371 log_error("%s: failed to write strings.", p
);
378 log_error("%s: failed to write database.", p
);
382 fchmod(fileno(w
), 0644);
384 if (rename(p
, database
) < 0) {
385 log_error_errno(errno
, "rename (%s -> %s) failed: %m", p
, database
);
397 int catalog_update(const char* database
, const char* root
, const char* const* dirs
) {
398 _cleanup_strv_free_
char **files
= NULL
;
400 struct strbuf
*sb
= NULL
;
401 _cleanup_hashmap_free_free_ Hashmap
*h
= NULL
;
402 _cleanup_free_ CatalogItem
*items
= NULL
;
408 h
= hashmap_new(&catalog_hash_ops
);
416 r
= conf_files_list_strv(&files
, ".catalog", root
, dirs
);
418 log_error_errno(r
, "Failed to get catalog files: %m");
422 STRV_FOREACH(f
, files
) {
423 log_debug("Reading file '%s'", *f
);
424 r
= catalog_import_file(h
, sb
, *f
);
426 log_error("Failed to import file '%s': %s.",
432 if (hashmap_size(h
) <= 0) {
433 log_info("No items in catalog.");
436 log_debug("Found %u items in catalog.", hashmap_size(h
));
440 items
= new(CatalogItem
, hashmap_size(h
));
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
);
454 assert(n
== hashmap_size(h
));
455 qsort_safe(items
, n
, sizeof(CatalogItem
), catalog_compare_func
);
457 r
= write_catalog(database
, h
, sb
, items
, n
);
459 log_error_errno(r
, "Failed to write %s: %m", database
);
461 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
462 database
, n
, sb
->len
, r
);
468 return r
< 0 ? r
: 0;
471 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
472 const CatalogHeader
*h
;
481 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
485 if (fstat(fd
, &st
) < 0) {
490 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
495 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
496 if (p
== MAP_FAILED
) {
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
))) {
509 munmap(p
, st
.st_size
);
520 static const char *find_id(void *p
, sd_id128_t id
) {
521 CatalogItem key
, *f
= NULL
;
522 const CatalogHeader
*h
= p
;
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;
533 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
537 e
= strchr(key
.language
, '_');
540 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
547 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
553 return (const char*) p
+
554 le64toh(h
->header_size
) +
555 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
559 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
560 _cleanup_close_
int fd
= -1;
569 r
= open_mmap(database
, &fd
, &st
, &p
);
590 munmap(p
, st
.st_size
);
595 static char *find_header(const char *s
, const char *header
) {
600 v
= startswith(s
, header
);
602 v
+= strspn(v
, WHITESPACE
);
603 return strndup(v
, strcspn(v
, NEWLINE
));
619 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
621 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
623 subject
= find_header(s
, "Subject:");
624 defined_by
= find_header(s
, "Defined-By:");
626 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
627 SD_ID128_FORMAT_VAL(id
),
628 strna(defined_by
), strna(subject
));
630 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
631 SD_ID128_FORMAT_VAL(id
), s
);
635 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
636 _cleanup_close_
int fd
= -1;
639 const CatalogHeader
*h
;
640 const CatalogItem
*items
;
644 bool last_id_set
= false;
646 r
= open_mmap(database
, &fd
, &st
, &p
);
651 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
653 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
656 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
659 assert_se(s
= find_id(p
, items
[n
].id
));
661 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
664 last_id
= items
[n
].id
;
667 munmap(p
, st
.st_size
);
672 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
676 STRV_FOREACH(item
, items
) {
679 _cleanup_free_
char *msg
= NULL
;
681 k
= sd_id128_from_string(*item
, &id
);
683 log_error_errno(k
, "Failed to parse id128 '%s': %m",
690 k
= catalog_get(database
, id
, &msg
);
692 log_full(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
,
693 "Failed to retrieve catalog entry for '%s': %s",
694 *item
, strerror(-k
));
700 dump_catalog_entry(f
, id
, msg
, oneline
);