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"
37 #include "conf-files.h"
40 #include "siphash24.h"
42 const char * const catalog_file_dirs
[] = {
43 "/usr/local/lib/systemd/catalog/",
44 "/usr/lib/systemd/catalog/",
48 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
50 typedef struct CatalogHeader
{
51 uint8_t signature
[8]; /* "RHHHKSLP" */
52 le32_t compatible_flags
;
53 le32_t incompatible_flags
;
56 le64_t catalog_item_size
;
59 typedef struct CatalogItem
{
65 static void catalog_hash_func(const void *p
, struct siphash
*state
) {
66 const CatalogItem
*i
= p
;
68 siphash24_compress(&i
->id
, sizeof(i
->id
), state
);
69 siphash24_compress(i
->language
, strlen(i
->language
), state
);
72 static int catalog_compare_func(const void *a
, const void *b
) {
73 const CatalogItem
*i
= a
, *j
= b
;
76 for (k
= 0; k
< ELEMENTSOF(j
->id
.bytes
); k
++) {
77 if (i
->id
.bytes
[k
] < j
->id
.bytes
[k
])
79 if (i
->id
.bytes
[k
] > j
->id
.bytes
[k
])
83 return strcmp(i
->language
, j
->language
);
86 const struct hash_ops catalog_hash_ops
= {
87 .hash
= catalog_hash_func
,
88 .compare
= catalog_compare_func
91 static int finish_item(
96 const char *payload
) {
99 _cleanup_free_ CatalogItem
*i
= NULL
;
106 offset
= strbuf_add_string(sb
, payload
, strlen(payload
));
110 i
= new0(CatalogItem
, 1);
116 assert(strlen(language
) > 1 && strlen(language
) < 32);
117 strcpy(i
->language
, language
);
119 i
->offset
= htole64((uint64_t) offset
);
121 r
= hashmap_put(h
, i
, i
);
123 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR
".%s, ignoring.",
124 SD_ID128_FORMAT_VAL(id
), language
? language
: "C");
133 int catalog_file_lang(const char* filename
, char **lang
) {
134 char *beg
, *end
, *_lang
;
136 end
= endswith(filename
, ".catalog");
141 while (beg
> filename
&& *beg
!= '.' && *beg
!= '/' && end
- beg
< 32)
144 if (*beg
!= '.' || end
<= beg
+ 1)
147 _lang
= strndup(beg
+ 1, end
- beg
- 1);
155 static int catalog_entry_lang(const char* filename
, int line
,
156 const char* t
, const char* deflang
, char **lang
) {
161 log_error("[%s:%u] Language too short.", filename
, line
);
165 log_error("[%s:%u] language too long.", filename
, line
);
170 if (streq(t
, deflang
)) {
171 log_warning("[%s:%u] language specified unnecessarily",
175 log_warning("[%s:%u] language differs from default for file",
186 int catalog_import_file(Hashmap
*h
, struct strbuf
*sb
, const char *path
) {
187 _cleanup_fclose_
FILE *f
= NULL
;
188 _cleanup_free_
char *payload
= NULL
;
191 _cleanup_free_
char *deflang
= NULL
, *lang
= NULL
;
192 bool got_id
= false, empty_line
= true;
199 f
= fopen(path
, "re");
201 return log_error_errno(errno
, "Failed to open file %s: %m", path
);
203 r
= catalog_file_lang(path
, &deflang
);
205 log_error_errno(errno
, "Failed to determine language for file %s: %m", path
);
207 log_debug("File %s has language %s.", path
, deflang
);
214 if (!fgets(line
, sizeof(line
), f
)) {
218 log_error_errno(errno
, "Failed to read file %s: %m", path
);
231 if (strchr(COMMENTS
"\n", line
[0]))
235 strlen(line
) >= 2+1+32 &&
239 (line
[2+1+32] == ' ' || line
[2+1+32] == '\0')) {
246 with_language
= line
[2+1+32] != '\0';
249 if (sd_id128_from_string(line
+ 2 + 1, &jd
) >= 0) {
252 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
260 t
= strstrip(line
+ 2 + 1 + 32 + 1);
262 r
= catalog_entry_lang(path
, n
, t
, deflang
, &lang
);
280 log_error("[%s:%u] Got payload before ID.", path
, n
);
284 a
= payload
? strlen(payload
) : 0;
287 c
= a
+ (empty_line
? 1 : 0) + b
+ 1 + 1;
288 t
= realloc(payload
, c
);
294 memcpy(t
+ a
+ 1, line
, b
);
298 memcpy(t
+ a
, line
, b
);
308 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
316 static long write_catalog(const char *database
, Hashmap
*h
, struct strbuf
*sb
,
317 CatalogItem
*items
, size_t n
) {
318 CatalogHeader header
;
319 _cleanup_fclose_
FILE *w
= NULL
;
321 _cleanup_free_
char *d
, *p
= NULL
;
324 d
= dirname_malloc(database
);
328 r
= mkdir_p(d
, 0775);
330 return log_error_errno(r
, "Recursive mkdir %s: %m", d
);
332 r
= fopen_temporary(database
, &w
, &p
);
334 return log_error_errno(r
, "Failed to open database for writing: %s: %m",
338 memcpy(header
.signature
, CATALOG_SIGNATURE
, sizeof(header
.signature
));
339 header
.header_size
= htole64(ALIGN_TO(sizeof(CatalogHeader
), 8));
340 header
.catalog_item_size
= htole64(sizeof(CatalogItem
));
341 header
.n_items
= htole64(hashmap_size(h
));
345 k
= fwrite(&header
, 1, sizeof(header
), w
);
346 if (k
!= sizeof(header
)) {
347 log_error("%s: failed to write header.", p
);
351 k
= fwrite(items
, 1, n
* sizeof(CatalogItem
), w
);
352 if (k
!= n
* sizeof(CatalogItem
)) {
353 log_error("%s: failed to write database.", p
);
357 k
= fwrite(sb
->buf
, 1, sb
->len
, w
);
359 log_error("%s: failed to write strings.", p
);
363 r
= fflush_and_check(w
);
365 log_error_errno(r
, "%s: failed to write database: %m", p
);
369 fchmod(fileno(w
), 0644);
371 if (rename(p
, database
) < 0) {
372 r
= log_error_errno(errno
, "rename (%s -> %s) failed: %m", p
, database
);
383 int catalog_update(const char* database
, const char* root
, const char* const* dirs
) {
384 _cleanup_strv_free_
char **files
= NULL
;
386 struct strbuf
*sb
= NULL
;
387 _cleanup_hashmap_free_free_ Hashmap
*h
= NULL
;
388 _cleanup_free_ CatalogItem
*items
= NULL
;
394 h
= hashmap_new(&catalog_hash_ops
);
402 r
= conf_files_list_strv(&files
, ".catalog", root
, dirs
);
404 log_error_errno(r
, "Failed to get catalog files: %m");
408 STRV_FOREACH(f
, files
) {
409 log_debug("Reading file '%s'", *f
);
410 r
= catalog_import_file(h
, sb
, *f
);
412 log_error_errno(r
, "Failed to import file '%s': %m", *f
);
417 if (hashmap_size(h
) <= 0) {
418 log_info("No items in catalog.");
421 log_debug("Found %u items in catalog.", hashmap_size(h
));
425 items
= new(CatalogItem
, hashmap_size(h
));
432 HASHMAP_FOREACH(i
, h
, j
) {
433 log_debug("Found " SD_ID128_FORMAT_STR
", language %s",
434 SD_ID128_FORMAT_VAL(i
->id
),
435 isempty(i
->language
) ? "C" : i
->language
);
439 assert(n
== hashmap_size(h
));
440 qsort_safe(items
, n
, sizeof(CatalogItem
), catalog_compare_func
);
442 r
= write_catalog(database
, h
, sb
, items
, n
);
444 log_error_errno(r
, "Failed to write %s: %m", database
);
446 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
447 database
, n
, sb
->len
, r
);
453 return r
< 0 ? r
: 0;
456 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
457 const CatalogHeader
*h
;
466 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
470 if (fstat(fd
, &st
) < 0) {
475 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
480 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
481 if (p
== MAP_FAILED
) {
487 if (memcmp(h
->signature
, CATALOG_SIGNATURE
, sizeof(h
->signature
)) != 0 ||
488 le64toh(h
->header_size
) < sizeof(CatalogHeader
) ||
489 le64toh(h
->catalog_item_size
) < sizeof(CatalogItem
) ||
490 h
->incompatible_flags
!= 0 ||
491 le64toh(h
->n_items
) <= 0 ||
492 st
.st_size
< (off_t
) (le64toh(h
->header_size
) + le64toh(h
->catalog_item_size
) * le64toh(h
->n_items
))) {
494 munmap(p
, st
.st_size
);
505 static const char *find_id(void *p
, sd_id128_t id
) {
506 CatalogItem key
, *f
= NULL
;
507 const CatalogHeader
*h
= p
;
513 loc
= setlocale(LC_MESSAGES
, NULL
);
514 if (loc
&& loc
[0] && !streq(loc
, "C") && !streq(loc
, "POSIX")) {
515 strncpy(key
.language
, loc
, sizeof(key
.language
));
516 key
.language
[strcspn(key
.language
, ".@")] = 0;
518 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
522 e
= strchr(key
.language
, '_');
525 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
532 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
538 return (const char*) p
+
539 le64toh(h
->header_size
) +
540 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
544 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
545 _cleanup_close_
int fd
= -1;
554 r
= open_mmap(database
, &fd
, &st
, &p
);
575 munmap(p
, st
.st_size
);
580 static char *find_header(const char *s
, const char *header
) {
585 v
= startswith(s
, header
);
587 v
+= strspn(v
, WHITESPACE
);
588 return strndup(v
, strcspn(v
, NEWLINE
));
604 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
606 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
608 subject
= find_header(s
, "Subject:");
609 defined_by
= find_header(s
, "Defined-By:");
611 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
612 SD_ID128_FORMAT_VAL(id
),
613 strna(defined_by
), strna(subject
));
615 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
616 SD_ID128_FORMAT_VAL(id
), s
);
620 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
621 _cleanup_close_
int fd
= -1;
624 const CatalogHeader
*h
;
625 const CatalogItem
*items
;
629 bool last_id_set
= false;
631 r
= open_mmap(database
, &fd
, &st
, &p
);
636 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
638 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
641 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
644 assert_se(s
= find_id(p
, items
[n
].id
));
646 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
649 last_id
= items
[n
].id
;
652 munmap(p
, st
.st_size
);
657 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
661 STRV_FOREACH(item
, items
) {
664 _cleanup_free_
char *msg
= NULL
;
666 k
= sd_id128_from_string(*item
, &id
);
668 log_error_errno(k
, "Failed to parse id128 '%s': %m", *item
);
674 k
= catalog_get(database
, id
, &msg
);
676 log_full_errno(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
, k
,
677 "Failed to retrieve catalog entry for '%s': %m", *item
);
683 dump_catalog_entry(f
, id
, msg
, oneline
);