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 "alloc-util.h"
34 #include "conf-files.h"
40 #include "path-util.h"
41 #include "siphash24.h"
42 #include "sparse-endian.h"
44 #include "string-util.h"
48 const char * const catalog_file_dirs
[] = {
49 "/usr/local/lib/systemd/catalog/",
50 "/usr/lib/systemd/catalog/",
54 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
56 typedef struct CatalogHeader
{
57 uint8_t signature
[8]; /* "RHHHKSLP" */
58 le32_t compatible_flags
;
59 le32_t incompatible_flags
;
62 le64_t catalog_item_size
;
65 typedef struct CatalogItem
{
71 static void catalog_hash_func(const void *p
, struct siphash
*state
) {
72 const CatalogItem
*i
= p
;
74 siphash24_compress(&i
->id
, sizeof(i
->id
), state
);
75 siphash24_compress(i
->language
, strlen(i
->language
), state
);
78 static int catalog_compare_func(const void *a
, const void *b
) {
79 const CatalogItem
*i
= a
, *j
= b
;
82 for (k
= 0; k
< ELEMENTSOF(j
->id
.bytes
); k
++) {
83 if (i
->id
.bytes
[k
] < j
->id
.bytes
[k
])
85 if (i
->id
.bytes
[k
] > j
->id
.bytes
[k
])
89 return strcmp(i
->language
, j
->language
);
92 const struct hash_ops catalog_hash_ops
= {
93 .hash
= catalog_hash_func
,
94 .compare
= catalog_compare_func
97 static int finish_item(
101 const char *language
,
102 const char *payload
) {
105 _cleanup_free_ CatalogItem
*i
= NULL
;
112 offset
= strbuf_add_string(sb
, payload
, strlen(payload
));
116 i
= new0(CatalogItem
, 1);
122 assert(strlen(language
) > 1 && strlen(language
) < 32);
123 strcpy(i
->language
, language
);
125 i
->offset
= htole64((uint64_t) offset
);
127 r
= hashmap_put(h
, i
, i
);
129 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR
".%s, ignoring.",
130 SD_ID128_FORMAT_VAL(id
), language
? language
: "C");
139 int catalog_file_lang(const char* filename
, char **lang
) {
140 char *beg
, *end
, *_lang
;
142 end
= endswith(filename
, ".catalog");
147 while (beg
> filename
&& *beg
!= '.' && *beg
!= '/' && end
- beg
< 32)
150 if (*beg
!= '.' || end
<= beg
+ 1)
153 _lang
= strndup(beg
+ 1, end
- beg
- 1);
161 static int catalog_entry_lang(const char* filename
, int line
,
162 const char* t
, const char* deflang
, char **lang
) {
167 log_error("[%s:%u] Language too short.", filename
, line
);
171 log_error("[%s:%u] language too long.", filename
, line
);
176 if (streq(t
, deflang
)) {
177 log_warning("[%s:%u] language specified unnecessarily",
181 log_warning("[%s:%u] language differs from default for file",
192 int catalog_import_file(Hashmap
*h
, struct strbuf
*sb
, const char *path
) {
193 _cleanup_fclose_
FILE *f
= NULL
;
194 _cleanup_free_
char *payload
= NULL
;
197 _cleanup_free_
char *deflang
= NULL
, *lang
= NULL
;
198 bool got_id
= false, empty_line
= true;
205 f
= fopen(path
, "re");
207 return log_error_errno(errno
, "Failed to open file %s: %m", path
);
209 r
= catalog_file_lang(path
, &deflang
);
211 log_error_errno(errno
, "Failed to determine language for file %s: %m", path
);
213 log_debug("File %s has language %s.", path
, deflang
);
220 if (!fgets(line
, sizeof(line
), f
)) {
224 log_error_errno(errno
, "Failed to read file %s: %m", path
);
237 if (strchr(COMMENTS
"\n", line
[0]))
241 strlen(line
) >= 2+1+32 &&
245 (line
[2+1+32] == ' ' || line
[2+1+32] == '\0')) {
252 with_language
= line
[2+1+32] != '\0';
255 if (sd_id128_from_string(line
+ 2 + 1, &jd
) >= 0) {
258 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
266 t
= strstrip(line
+ 2 + 1 + 32 + 1);
268 r
= catalog_entry_lang(path
, n
, t
, deflang
, &lang
);
286 log_error("[%s:%u] Got payload before ID.", path
, n
);
290 a
= payload
? strlen(payload
) : 0;
293 c
= a
+ (empty_line
? 1 : 0) + b
+ 1 + 1;
294 t
= realloc(payload
, c
);
300 memcpy(t
+ a
+ 1, line
, b
);
304 memcpy(t
+ a
, line
, b
);
314 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
322 static int64_t write_catalog(const char *database
, struct strbuf
*sb
,
323 CatalogItem
*items
, size_t n
) {
324 CatalogHeader header
;
325 _cleanup_fclose_
FILE *w
= NULL
;
327 _cleanup_free_
char *d
, *p
= NULL
;
330 d
= dirname_malloc(database
);
334 r
= mkdir_p(d
, 0775);
336 return log_error_errno(r
, "Recursive mkdir %s: %m", d
);
338 r
= fopen_temporary(database
, &w
, &p
);
340 return log_error_errno(r
, "Failed to open database for writing: %s: %m",
344 memcpy(header
.signature
, CATALOG_SIGNATURE
, sizeof(header
.signature
));
345 header
.header_size
= htole64(ALIGN_TO(sizeof(CatalogHeader
), 8));
346 header
.catalog_item_size
= htole64(sizeof(CatalogItem
));
347 header
.n_items
= htole64(n
);
351 k
= fwrite(&header
, 1, sizeof(header
), w
);
352 if (k
!= sizeof(header
)) {
353 log_error("%s: failed to write header.", p
);
357 k
= fwrite(items
, 1, n
* sizeof(CatalogItem
), w
);
358 if (k
!= n
* sizeof(CatalogItem
)) {
359 log_error("%s: failed to write database.", p
);
363 k
= fwrite(sb
->buf
, 1, sb
->len
, w
);
365 log_error("%s: failed to write strings.", p
);
369 r
= fflush_and_check(w
);
371 log_error_errno(r
, "%s: failed to write database: %m", p
);
375 fchmod(fileno(w
), 0644);
377 if (rename(p
, database
) < 0) {
378 r
= log_error_errno(errno
, "rename (%s -> %s) failed: %m", p
, database
);
389 int catalog_update(const char* database
, const char* root
, const char* const* dirs
) {
390 _cleanup_strv_free_
char **files
= NULL
;
392 struct strbuf
*sb
= NULL
;
393 _cleanup_hashmap_free_free_ Hashmap
*h
= NULL
;
394 _cleanup_free_ CatalogItem
*items
= NULL
;
401 h
= hashmap_new(&catalog_hash_ops
);
409 r
= conf_files_list_strv(&files
, ".catalog", root
, dirs
);
411 log_error_errno(r
, "Failed to get catalog files: %m");
415 STRV_FOREACH(f
, files
) {
416 log_debug("Reading file '%s'", *f
);
417 r
= catalog_import_file(h
, sb
, *f
);
419 log_error_errno(r
, "Failed to import file '%s': %m", *f
);
424 if (hashmap_size(h
) <= 0) {
425 log_info("No items in catalog.");
428 log_debug("Found %u items in catalog.", hashmap_size(h
));
432 items
= new(CatalogItem
, hashmap_size(h
));
439 HASHMAP_FOREACH(i
, h
, j
) {
440 log_debug("Found " SD_ID128_FORMAT_STR
", language %s",
441 SD_ID128_FORMAT_VAL(i
->id
),
442 isempty(i
->language
) ? "C" : i
->language
);
446 assert(n
== hashmap_size(h
));
447 qsort_safe(items
, n
, sizeof(CatalogItem
), catalog_compare_func
);
449 sz
= write_catalog(database
, sb
, items
, n
);
451 r
= log_error_errno(sz
, "Failed to write %s: %m", database
);
454 log_debug("%s: wrote %u items, with %zu bytes of strings, %"PRIi64
" total size.",
455 database
, n
, sb
->len
, sz
);
464 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
465 const CatalogHeader
*h
;
474 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
478 if (fstat(fd
, &st
) < 0) {
483 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
488 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
489 if (p
== MAP_FAILED
) {
495 if (memcmp(h
->signature
, CATALOG_SIGNATURE
, sizeof(h
->signature
)) != 0 ||
496 le64toh(h
->header_size
) < sizeof(CatalogHeader
) ||
497 le64toh(h
->catalog_item_size
) < sizeof(CatalogItem
) ||
498 h
->incompatible_flags
!= 0 ||
499 le64toh(h
->n_items
) <= 0 ||
500 st
.st_size
< (off_t
) (le64toh(h
->header_size
) + le64toh(h
->catalog_item_size
) * le64toh(h
->n_items
))) {
502 munmap(p
, st
.st_size
);
513 static const char *find_id(void *p
, sd_id128_t id
) {
514 CatalogItem key
, *f
= NULL
;
515 const CatalogHeader
*h
= p
;
521 loc
= setlocale(LC_MESSAGES
, NULL
);
522 if (loc
&& loc
[0] && !streq(loc
, "C") && !streq(loc
, "POSIX")) {
523 strncpy(key
.language
, loc
, sizeof(key
.language
));
524 key
.language
[strcspn(key
.language
, ".@")] = 0;
526 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
530 e
= strchr(key
.language
, '_');
533 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
540 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
546 return (const char*) p
+
547 le64toh(h
->header_size
) +
548 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
552 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
553 _cleanup_close_
int fd
= -1;
562 r
= open_mmap(database
, &fd
, &st
, &p
);
583 munmap(p
, st
.st_size
);
588 static char *find_header(const char *s
, const char *header
) {
593 v
= startswith(s
, header
);
595 v
+= strspn(v
, WHITESPACE
);
596 return strndup(v
, strcspn(v
, NEWLINE
));
612 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
614 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
616 subject
= find_header(s
, "Subject:");
617 defined_by
= find_header(s
, "Defined-By:");
619 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
620 SD_ID128_FORMAT_VAL(id
),
621 strna(defined_by
), strna(subject
));
623 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
624 SD_ID128_FORMAT_VAL(id
), s
);
628 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
629 _cleanup_close_
int fd
= -1;
632 const CatalogHeader
*h
;
633 const CatalogItem
*items
;
637 bool last_id_set
= false;
639 r
= open_mmap(database
, &fd
, &st
, &p
);
644 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
646 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
649 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
652 assert_se(s
= find_id(p
, items
[n
].id
));
654 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
657 last_id
= items
[n
].id
;
660 munmap(p
, st
.st_size
);
665 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
669 STRV_FOREACH(item
, items
) {
672 _cleanup_free_
char *msg
= NULL
;
674 k
= sd_id128_from_string(*item
, &id
);
676 log_error_errno(k
, "Failed to parse id128 '%s': %m", *item
);
682 k
= catalog_get(database
, id
, &msg
);
684 log_full_errno(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
, k
,
685 "Failed to retrieve catalog entry for '%s': %m", *item
);
691 dump_catalog_entry(f
, id
, msg
, oneline
);