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_errno(r
, "Failed to import file '%s': %m", *f
);
427 if (hashmap_size(h
) <= 0) {
428 log_info("No items in catalog.");
431 log_debug("Found %u items in catalog.", hashmap_size(h
));
435 items
= new(CatalogItem
, hashmap_size(h
));
442 HASHMAP_FOREACH(i
, h
, j
) {
443 log_debug("Found " SD_ID128_FORMAT_STR
", language %s",
444 SD_ID128_FORMAT_VAL(i
->id
),
445 isempty(i
->language
) ? "C" : i
->language
);
449 assert(n
== hashmap_size(h
));
450 qsort_safe(items
, n
, sizeof(CatalogItem
), catalog_compare_func
);
452 r
= write_catalog(database
, h
, sb
, items
, n
);
454 log_error_errno(r
, "Failed to write %s: %m", database
);
456 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
457 database
, n
, sb
->len
, r
);
463 return r
< 0 ? r
: 0;
466 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
467 const CatalogHeader
*h
;
476 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
480 if (fstat(fd
, &st
) < 0) {
485 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
490 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
491 if (p
== MAP_FAILED
) {
497 if (memcmp(h
->signature
, CATALOG_SIGNATURE
, sizeof(h
->signature
)) != 0 ||
498 le64toh(h
->header_size
) < sizeof(CatalogHeader
) ||
499 le64toh(h
->catalog_item_size
) < sizeof(CatalogItem
) ||
500 h
->incompatible_flags
!= 0 ||
501 le64toh(h
->n_items
) <= 0 ||
502 st
.st_size
< (off_t
) (le64toh(h
->header_size
) + le64toh(h
->catalog_item_size
) * le64toh(h
->n_items
))) {
504 munmap(p
, st
.st_size
);
515 static const char *find_id(void *p
, sd_id128_t id
) {
516 CatalogItem key
, *f
= NULL
;
517 const CatalogHeader
*h
= p
;
523 loc
= setlocale(LC_MESSAGES
, NULL
);
524 if (loc
&& loc
[0] && !streq(loc
, "C") && !streq(loc
, "POSIX")) {
525 strncpy(key
.language
, loc
, sizeof(key
.language
));
526 key
.language
[strcspn(key
.language
, ".@")] = 0;
528 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
532 e
= strchr(key
.language
, '_');
535 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
542 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
548 return (const char*) p
+
549 le64toh(h
->header_size
) +
550 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
554 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
555 _cleanup_close_
int fd
= -1;
564 r
= open_mmap(database
, &fd
, &st
, &p
);
585 munmap(p
, st
.st_size
);
590 static char *find_header(const char *s
, const char *header
) {
595 v
= startswith(s
, header
);
597 v
+= strspn(v
, WHITESPACE
);
598 return strndup(v
, strcspn(v
, NEWLINE
));
614 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
616 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
618 subject
= find_header(s
, "Subject:");
619 defined_by
= find_header(s
, "Defined-By:");
621 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
622 SD_ID128_FORMAT_VAL(id
),
623 strna(defined_by
), strna(subject
));
625 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
626 SD_ID128_FORMAT_VAL(id
), s
);
630 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
631 _cleanup_close_
int fd
= -1;
634 const CatalogHeader
*h
;
635 const CatalogItem
*items
;
639 bool last_id_set
= false;
641 r
= open_mmap(database
, &fd
, &st
, &p
);
646 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
648 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
651 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
654 assert_se(s
= find_id(p
, items
[n
].id
));
656 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
659 last_id
= items
[n
].id
;
662 munmap(p
, st
.st_size
);
667 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
671 STRV_FOREACH(item
, items
) {
674 _cleanup_free_
char *msg
= NULL
;
676 k
= sd_id128_from_string(*item
, &id
);
678 log_error_errno(k
, "Failed to parse id128 '%s': %m", *item
);
684 k
= catalog_get(database
, id
, &msg
);
686 log_full_errno(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
, k
,
687 "Failed to retrieve catalog entry for '%s': %m", *item
);
693 dump_catalog_entry(f
, id
, msg
, oneline
);