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/>.
33 #include "conf-files.h"
37 #include "siphash24.h"
38 #include "sparse-endian.h"
40 #include "string-util.h"
44 const char * const catalog_file_dirs
[] = {
45 "/usr/local/lib/systemd/catalog/",
46 "/usr/lib/systemd/catalog/",
50 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
52 typedef struct CatalogHeader
{
53 uint8_t signature
[8]; /* "RHHHKSLP" */
54 le32_t compatible_flags
;
55 le32_t incompatible_flags
;
58 le64_t catalog_item_size
;
61 typedef struct CatalogItem
{
67 static void catalog_hash_func(const void *p
, struct siphash
*state
) {
68 const CatalogItem
*i
= p
;
70 siphash24_compress(&i
->id
, sizeof(i
->id
), state
);
71 siphash24_compress(i
->language
, strlen(i
->language
), state
);
74 static int catalog_compare_func(const void *a
, const void *b
) {
75 const CatalogItem
*i
= a
, *j
= b
;
78 for (k
= 0; k
< ELEMENTSOF(j
->id
.bytes
); k
++) {
79 if (i
->id
.bytes
[k
] < j
->id
.bytes
[k
])
81 if (i
->id
.bytes
[k
] > j
->id
.bytes
[k
])
85 return strcmp(i
->language
, j
->language
);
88 const struct hash_ops catalog_hash_ops
= {
89 .hash
= catalog_hash_func
,
90 .compare
= catalog_compare_func
93 static int finish_item(
98 const char *payload
) {
101 _cleanup_free_ CatalogItem
*i
= NULL
;
108 offset
= strbuf_add_string(sb
, payload
, strlen(payload
));
112 i
= new0(CatalogItem
, 1);
118 assert(strlen(language
) > 1 && strlen(language
) < 32);
119 strcpy(i
->language
, language
);
121 i
->offset
= htole64((uint64_t) offset
);
123 r
= hashmap_put(h
, i
, i
);
125 log_warning("Duplicate entry for " SD_ID128_FORMAT_STR
".%s, ignoring.",
126 SD_ID128_FORMAT_VAL(id
), language
? language
: "C");
135 int catalog_file_lang(const char* filename
, char **lang
) {
136 char *beg
, *end
, *_lang
;
138 end
= endswith(filename
, ".catalog");
143 while (beg
> filename
&& *beg
!= '.' && *beg
!= '/' && end
- beg
< 32)
146 if (*beg
!= '.' || end
<= beg
+ 1)
149 _lang
= strndup(beg
+ 1, end
- beg
- 1);
157 static int catalog_entry_lang(const char* filename
, int line
,
158 const char* t
, const char* deflang
, char **lang
) {
163 log_error("[%s:%u] Language too short.", filename
, line
);
167 log_error("[%s:%u] language too long.", filename
, line
);
172 if (streq(t
, deflang
)) {
173 log_warning("[%s:%u] language specified unnecessarily",
177 log_warning("[%s:%u] language differs from default for file",
188 int catalog_import_file(Hashmap
*h
, struct strbuf
*sb
, const char *path
) {
189 _cleanup_fclose_
FILE *f
= NULL
;
190 _cleanup_free_
char *payload
= NULL
;
193 _cleanup_free_
char *deflang
= NULL
, *lang
= NULL
;
194 bool got_id
= false, empty_line
= true;
201 f
= fopen(path
, "re");
203 return log_error_errno(errno
, "Failed to open file %s: %m", path
);
205 r
= catalog_file_lang(path
, &deflang
);
207 log_error_errno(errno
, "Failed to determine language for file %s: %m", path
);
209 log_debug("File %s has language %s.", path
, deflang
);
216 if (!fgets(line
, sizeof(line
), f
)) {
220 log_error_errno(errno
, "Failed to read file %s: %m", path
);
233 if (strchr(COMMENTS
"\n", line
[0]))
237 strlen(line
) >= 2+1+32 &&
241 (line
[2+1+32] == ' ' || line
[2+1+32] == '\0')) {
248 with_language
= line
[2+1+32] != '\0';
251 if (sd_id128_from_string(line
+ 2 + 1, &jd
) >= 0) {
254 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
262 t
= strstrip(line
+ 2 + 1 + 32 + 1);
264 r
= catalog_entry_lang(path
, n
, t
, deflang
, &lang
);
282 log_error("[%s:%u] Got payload before ID.", path
, n
);
286 a
= payload
? strlen(payload
) : 0;
289 c
= a
+ (empty_line
? 1 : 0) + b
+ 1 + 1;
290 t
= realloc(payload
, c
);
296 memcpy(t
+ a
+ 1, line
, b
);
300 memcpy(t
+ a
, line
, b
);
310 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
318 static long write_catalog(const char *database
, Hashmap
*h
, struct strbuf
*sb
,
319 CatalogItem
*items
, size_t n
) {
320 CatalogHeader header
;
321 _cleanup_fclose_
FILE *w
= NULL
;
323 _cleanup_free_
char *d
, *p
= NULL
;
326 d
= dirname_malloc(database
);
330 r
= mkdir_p(d
, 0775);
332 return log_error_errno(r
, "Recursive mkdir %s: %m", d
);
334 r
= fopen_temporary(database
, &w
, &p
);
336 return log_error_errno(r
, "Failed to open database for writing: %s: %m",
340 memcpy(header
.signature
, CATALOG_SIGNATURE
, sizeof(header
.signature
));
341 header
.header_size
= htole64(ALIGN_TO(sizeof(CatalogHeader
), 8));
342 header
.catalog_item_size
= htole64(sizeof(CatalogItem
));
343 header
.n_items
= htole64(hashmap_size(h
));
347 k
= fwrite(&header
, 1, sizeof(header
), w
);
348 if (k
!= sizeof(header
)) {
349 log_error("%s: failed to write header.", p
);
353 k
= fwrite(items
, 1, n
* sizeof(CatalogItem
), w
);
354 if (k
!= n
* sizeof(CatalogItem
)) {
355 log_error("%s: failed to write database.", p
);
359 k
= fwrite(sb
->buf
, 1, sb
->len
, w
);
361 log_error("%s: failed to write strings.", p
);
365 r
= fflush_and_check(w
);
367 log_error_errno(r
, "%s: failed to write database: %m", p
);
371 fchmod(fileno(w
), 0644);
373 if (rename(p
, database
) < 0) {
374 r
= log_error_errno(errno
, "rename (%s -> %s) failed: %m", p
, database
);
385 int catalog_update(const char* database
, const char* root
, const char* const* dirs
) {
386 _cleanup_strv_free_
char **files
= NULL
;
388 struct strbuf
*sb
= NULL
;
389 _cleanup_hashmap_free_free_ Hashmap
*h
= NULL
;
390 _cleanup_free_ CatalogItem
*items
= NULL
;
396 h
= hashmap_new(&catalog_hash_ops
);
404 r
= conf_files_list_strv(&files
, ".catalog", root
, dirs
);
406 log_error_errno(r
, "Failed to get catalog files: %m");
410 STRV_FOREACH(f
, files
) {
411 log_debug("Reading file '%s'", *f
);
412 r
= catalog_import_file(h
, sb
, *f
);
414 log_error_errno(r
, "Failed to import file '%s': %m", *f
);
419 if (hashmap_size(h
) <= 0) {
420 log_info("No items in catalog.");
423 log_debug("Found %u items in catalog.", hashmap_size(h
));
427 items
= new(CatalogItem
, hashmap_size(h
));
434 HASHMAP_FOREACH(i
, h
, j
) {
435 log_debug("Found " SD_ID128_FORMAT_STR
", language %s",
436 SD_ID128_FORMAT_VAL(i
->id
),
437 isempty(i
->language
) ? "C" : i
->language
);
441 assert(n
== hashmap_size(h
));
442 qsort_safe(items
, n
, sizeof(CatalogItem
), catalog_compare_func
);
444 r
= write_catalog(database
, h
, sb
, items
, n
);
446 log_error_errno(r
, "Failed to write %s: %m", database
);
448 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
449 database
, n
, sb
->len
, r
);
455 return r
< 0 ? r
: 0;
458 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
459 const CatalogHeader
*h
;
468 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
472 if (fstat(fd
, &st
) < 0) {
477 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
482 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
483 if (p
== MAP_FAILED
) {
489 if (memcmp(h
->signature
, CATALOG_SIGNATURE
, sizeof(h
->signature
)) != 0 ||
490 le64toh(h
->header_size
) < sizeof(CatalogHeader
) ||
491 le64toh(h
->catalog_item_size
) < sizeof(CatalogItem
) ||
492 h
->incompatible_flags
!= 0 ||
493 le64toh(h
->n_items
) <= 0 ||
494 st
.st_size
< (off_t
) (le64toh(h
->header_size
) + le64toh(h
->catalog_item_size
) * le64toh(h
->n_items
))) {
496 munmap(p
, st
.st_size
);
507 static const char *find_id(void *p
, sd_id128_t id
) {
508 CatalogItem key
, *f
= NULL
;
509 const CatalogHeader
*h
= p
;
515 loc
= setlocale(LC_MESSAGES
, NULL
);
516 if (loc
&& loc
[0] && !streq(loc
, "C") && !streq(loc
, "POSIX")) {
517 strncpy(key
.language
, loc
, sizeof(key
.language
));
518 key
.language
[strcspn(key
.language
, ".@")] = 0;
520 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
524 e
= strchr(key
.language
, '_');
527 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
534 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
540 return (const char*) p
+
541 le64toh(h
->header_size
) +
542 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
546 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
547 _cleanup_close_
int fd
= -1;
556 r
= open_mmap(database
, &fd
, &st
, &p
);
577 munmap(p
, st
.st_size
);
582 static char *find_header(const char *s
, const char *header
) {
587 v
= startswith(s
, header
);
589 v
+= strspn(v
, WHITESPACE
);
590 return strndup(v
, strcspn(v
, NEWLINE
));
606 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
608 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
610 subject
= find_header(s
, "Subject:");
611 defined_by
= find_header(s
, "Defined-By:");
613 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
614 SD_ID128_FORMAT_VAL(id
),
615 strna(defined_by
), strna(subject
));
617 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
618 SD_ID128_FORMAT_VAL(id
), s
);
622 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
623 _cleanup_close_
int fd
= -1;
626 const CatalogHeader
*h
;
627 const CatalogItem
*items
;
631 bool last_id_set
= false;
633 r
= open_mmap(database
, &fd
, &st
, &p
);
638 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
640 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
643 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
646 assert_se(s
= find_id(p
, items
[n
].id
));
648 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
651 last_id
= items
[n
].id
;
654 munmap(p
, st
.st_size
);
659 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
663 STRV_FOREACH(item
, items
) {
666 _cleanup_free_
char *msg
= NULL
;
668 k
= sd_id128_from_string(*item
, &id
);
670 log_error_errno(k
, "Failed to parse id128 '%s': %m", *item
);
676 k
= catalog_get(database
, id
, &msg
);
678 log_full_errno(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
, k
,
679 "Failed to retrieve catalog entry for '%s': %m", *item
);
685 dump_catalog_entry(f
, id
, msg
, oneline
);