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 unsigned long catalog_hash_func(const void *p
, const uint8_t hash_key
[HASH_KEY_SIZE
]) {
66 const CatalogItem
*i
= p
;
71 l
= strlen(i
->language
);
72 sz
= sizeof(i
->id
) + l
;
75 memcpy(mempcpy(v
, &i
->id
, sizeof(i
->id
)), i
->language
, l
);
77 siphash24((uint8_t*) &u
, v
, sz
, hash_key
);
79 return (unsigned long) u
;
82 static int catalog_compare_func(const void *a
, const void *b
) {
83 const CatalogItem
*i
= a
, *j
= b
;
86 for (k
= 0; k
< ELEMENTSOF(j
->id
.bytes
); k
++) {
87 if (i
->id
.bytes
[k
] < j
->id
.bytes
[k
])
89 if (i
->id
.bytes
[k
] > j
->id
.bytes
[k
])
93 return strcmp(i
->language
, j
->language
);
96 const struct hash_ops catalog_hash_ops
= {
97 .hash
= catalog_hash_func
,
98 .compare
= catalog_compare_func
101 static int finish_item(
105 const char *language
,
106 const char *payload
) {
109 _cleanup_free_ CatalogItem
*i
= NULL
;
116 offset
= strbuf_add_string(sb
, payload
, strlen(payload
));
120 i
= new0(CatalogItem
, 1);
126 assert(strlen(language
) > 1 && strlen(language
) < 32);
127 strcpy(i
->language
, language
);
129 i
->offset
= htole64((uint64_t) offset
);
131 r
= hashmap_put(h
, i
, i
);
133 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR
".%s, ignoring.",
134 SD_ID128_FORMAT_VAL(id
), language
? language
: "C");
143 int catalog_file_lang(const char* filename
, char **lang
) {
144 char *beg
, *end
, *_lang
;
146 end
= endswith(filename
, ".catalog");
151 while (beg
> filename
&& *beg
!= '.' && *beg
!= '/' && end
- beg
< 32)
154 if (*beg
!= '.' || end
<= beg
+ 1)
157 _lang
= strndup(beg
+ 1, end
- beg
- 1);
165 static int catalog_entry_lang(const char* filename
, int line
,
166 const char* t
, const char* deflang
, char **lang
) {
171 log_error("[%s:%u] Language too short.", filename
, line
);
175 log_error("[%s:%u] language too long.", filename
, line
);
180 if (streq(t
, deflang
)) {
181 log_warning("[%s:%u] language specified unnecessarily",
185 log_warning("[%s:%u] language differs from default for file",
196 int catalog_import_file(Hashmap
*h
, struct strbuf
*sb
, const char *path
) {
197 _cleanup_fclose_
FILE *f
= NULL
;
198 _cleanup_free_
char *payload
= NULL
;
201 _cleanup_free_
char *deflang
= NULL
, *lang
= NULL
;
202 bool got_id
= false, empty_line
= true;
209 f
= fopen(path
, "re");
211 return log_error_errno(errno
, "Failed to open file %s: %m", path
);
213 r
= catalog_file_lang(path
, &deflang
);
215 log_error_errno(errno
, "Failed to determine language for file %s: %m", path
);
217 log_debug("File %s has language %s.", path
, deflang
);
224 if (!fgets(line
, sizeof(line
), f
)) {
228 log_error_errno(errno
, "Failed to read file %s: %m", path
);
241 if (strchr(COMMENTS
"\n", line
[0]))
245 strlen(line
) >= 2+1+32 &&
249 (line
[2+1+32] == ' ' || line
[2+1+32] == '\0')) {
256 with_language
= line
[2+1+32] != '\0';
259 if (sd_id128_from_string(line
+ 2 + 1, &jd
) >= 0) {
262 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
270 t
= strstrip(line
+ 2 + 1 + 32 + 1);
272 r
= catalog_entry_lang(path
, n
, t
, deflang
, &lang
);
290 log_error("[%s:%u] Got payload before ID.", path
, n
);
294 a
= payload
? strlen(payload
) : 0;
297 c
= a
+ (empty_line
? 1 : 0) + b
+ 1 + 1;
298 t
= realloc(payload
, c
);
304 memcpy(t
+ a
+ 1, line
, b
);
308 memcpy(t
+ a
, line
, b
);
318 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
326 static long write_catalog(const char *database
, Hashmap
*h
, struct strbuf
*sb
,
327 CatalogItem
*items
, size_t n
) {
328 CatalogHeader header
;
329 _cleanup_fclose_
FILE *w
= NULL
;
331 _cleanup_free_
char *d
, *p
= NULL
;
334 d
= dirname_malloc(database
);
338 r
= mkdir_p(d
, 0775);
340 return log_error_errno(r
, "Recursive mkdir %s: %m", d
);
342 r
= fopen_temporary(database
, &w
, &p
);
344 return log_error_errno(r
, "Failed to open database for writing: %s: %m",
348 memcpy(header
.signature
, CATALOG_SIGNATURE
, sizeof(header
.signature
));
349 header
.header_size
= htole64(ALIGN_TO(sizeof(CatalogHeader
), 8));
350 header
.catalog_item_size
= htole64(sizeof(CatalogItem
));
351 header
.n_items
= htole64(hashmap_size(h
));
355 k
= fwrite(&header
, 1, sizeof(header
), w
);
356 if (k
!= sizeof(header
)) {
357 log_error("%s: failed to write header.", p
);
361 k
= fwrite(items
, 1, n
* sizeof(CatalogItem
), w
);
362 if (k
!= n
* sizeof(CatalogItem
)) {
363 log_error("%s: failed to write database.", p
);
367 k
= fwrite(sb
->buf
, 1, sb
->len
, w
);
369 log_error("%s: failed to write strings.", p
);
373 r
= fflush_and_check(w
);
375 log_error_errno(r
, "%s: failed to write database: %m", p
);
379 fchmod(fileno(w
), 0644);
381 if (rename(p
, database
) < 0) {
382 r
= log_error_errno(errno
, "rename (%s -> %s) failed: %m", p
, database
);
393 int catalog_update(const char* database
, const char* root
, const char* const* dirs
) {
394 _cleanup_strv_free_
char **files
= NULL
;
396 struct strbuf
*sb
= NULL
;
397 _cleanup_hashmap_free_free_ Hashmap
*h
= NULL
;
398 _cleanup_free_ CatalogItem
*items
= NULL
;
404 h
= hashmap_new(&catalog_hash_ops
);
412 r
= conf_files_list_strv(&files
, ".catalog", root
, dirs
);
414 log_error_errno(r
, "Failed to get catalog files: %m");
418 STRV_FOREACH(f
, files
) {
419 log_debug("Reading file '%s'", *f
);
420 r
= catalog_import_file(h
, sb
, *f
);
422 log_error("Failed to import file '%s': %s.",
428 if (hashmap_size(h
) <= 0) {
429 log_info("No items in catalog.");
432 log_debug("Found %u items in catalog.", hashmap_size(h
));
436 items
= new(CatalogItem
, hashmap_size(h
));
443 HASHMAP_FOREACH(i
, h
, j
) {
444 log_debug("Found " SD_ID128_FORMAT_STR
", language %s",
445 SD_ID128_FORMAT_VAL(i
->id
),
446 isempty(i
->language
) ? "C" : i
->language
);
450 assert(n
== hashmap_size(h
));
451 qsort_safe(items
, n
, sizeof(CatalogItem
), catalog_compare_func
);
453 r
= write_catalog(database
, h
, sb
, items
, n
);
455 log_error_errno(r
, "Failed to write %s: %m", database
);
457 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
458 database
, n
, sb
->len
, r
);
464 return r
< 0 ? r
: 0;
467 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
468 const CatalogHeader
*h
;
477 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
481 if (fstat(fd
, &st
) < 0) {
486 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
491 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
492 if (p
== MAP_FAILED
) {
498 if (memcmp(h
->signature
, CATALOG_SIGNATURE
, sizeof(h
->signature
)) != 0 ||
499 le64toh(h
->header_size
) < sizeof(CatalogHeader
) ||
500 le64toh(h
->catalog_item_size
) < sizeof(CatalogItem
) ||
501 h
->incompatible_flags
!= 0 ||
502 le64toh(h
->n_items
) <= 0 ||
503 st
.st_size
< (off_t
) (le64toh(h
->header_size
) + le64toh(h
->catalog_item_size
) * le64toh(h
->n_items
))) {
505 munmap(p
, st
.st_size
);
516 static const char *find_id(void *p
, sd_id128_t id
) {
517 CatalogItem key
, *f
= NULL
;
518 const CatalogHeader
*h
= p
;
524 loc
= setlocale(LC_MESSAGES
, NULL
);
525 if (loc
&& loc
[0] && !streq(loc
, "C") && !streq(loc
, "POSIX")) {
526 strncpy(key
.language
, loc
, sizeof(key
.language
));
527 key
.language
[strcspn(key
.language
, ".@")] = 0;
529 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
533 e
= strchr(key
.language
, '_');
536 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
543 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
549 return (const char*) p
+
550 le64toh(h
->header_size
) +
551 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
555 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
556 _cleanup_close_
int fd
= -1;
565 r
= open_mmap(database
, &fd
, &st
, &p
);
586 munmap(p
, st
.st_size
);
591 static char *find_header(const char *s
, const char *header
) {
596 v
= startswith(s
, header
);
598 v
+= strspn(v
, WHITESPACE
);
599 return strndup(v
, strcspn(v
, NEWLINE
));
615 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
617 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
619 subject
= find_header(s
, "Subject:");
620 defined_by
= find_header(s
, "Defined-By:");
622 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
623 SD_ID128_FORMAT_VAL(id
),
624 strna(defined_by
), strna(subject
));
626 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
627 SD_ID128_FORMAT_VAL(id
), s
);
631 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
632 _cleanup_close_
int fd
= -1;
635 const CatalogHeader
*h
;
636 const CatalogItem
*items
;
640 bool last_id_set
= false;
642 r
= open_mmap(database
, &fd
, &st
, &p
);
647 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
649 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
652 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
655 assert_se(s
= find_id(p
, items
[n
].id
));
657 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
660 last_id
= items
[n
].id
;
663 munmap(p
, st
.st_size
);
668 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
672 STRV_FOREACH(item
, items
) {
675 _cleanup_free_
char *msg
= NULL
;
677 k
= sd_id128_from_string(*item
, &id
);
679 log_error_errno(k
, "Failed to parse id128 '%s': %m",
686 k
= catalog_get(database
, id
, &msg
);
688 log_full(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
,
689 "Failed to retrieve catalog entry for '%s': %s",
690 *item
, strerror(-k
));
696 dump_catalog_entry(f
, id
, msg
, oneline
);