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 long write_catalog(const char *database
, Hashmap
*h
, 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(hashmap_size(h
));
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
;
400 h
= hashmap_new(&catalog_hash_ops
);
408 r
= conf_files_list_strv(&files
, ".catalog", root
, dirs
);
410 log_error_errno(r
, "Failed to get catalog files: %m");
414 STRV_FOREACH(f
, files
) {
415 log_debug("Reading file '%s'", *f
);
416 r
= catalog_import_file(h
, sb
, *f
);
418 log_error_errno(r
, "Failed to import file '%s': %m", *f
);
423 if (hashmap_size(h
) <= 0) {
424 log_info("No items in catalog.");
427 log_debug("Found %u items in catalog.", hashmap_size(h
));
431 items
= new(CatalogItem
, hashmap_size(h
));
438 HASHMAP_FOREACH(i
, h
, j
) {
439 log_debug("Found " SD_ID128_FORMAT_STR
", language %s",
440 SD_ID128_FORMAT_VAL(i
->id
),
441 isempty(i
->language
) ? "C" : i
->language
);
445 assert(n
== hashmap_size(h
));
446 qsort_safe(items
, n
, sizeof(CatalogItem
), catalog_compare_func
);
448 r
= write_catalog(database
, h
, sb
, items
, n
);
450 log_error_errno(r
, "Failed to write %s: %m", database
);
452 log_debug("%s: wrote %u items, with %zu bytes of strings, %ld total size.",
453 database
, n
, sb
->len
, r
);
459 return r
< 0 ? r
: 0;
462 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
463 const CatalogHeader
*h
;
472 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
476 if (fstat(fd
, &st
) < 0) {
481 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
486 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
487 if (p
== MAP_FAILED
) {
493 if (memcmp(h
->signature
, CATALOG_SIGNATURE
, sizeof(h
->signature
)) != 0 ||
494 le64toh(h
->header_size
) < sizeof(CatalogHeader
) ||
495 le64toh(h
->catalog_item_size
) < sizeof(CatalogItem
) ||
496 h
->incompatible_flags
!= 0 ||
497 le64toh(h
->n_items
) <= 0 ||
498 st
.st_size
< (off_t
) (le64toh(h
->header_size
) + le64toh(h
->catalog_item_size
) * le64toh(h
->n_items
))) {
500 munmap(p
, st
.st_size
);
511 static const char *find_id(void *p
, sd_id128_t id
) {
512 CatalogItem key
, *f
= NULL
;
513 const CatalogHeader
*h
= p
;
519 loc
= setlocale(LC_MESSAGES
, NULL
);
520 if (loc
&& loc
[0] && !streq(loc
, "C") && !streq(loc
, "POSIX")) {
521 strncpy(key
.language
, loc
, sizeof(key
.language
));
522 key
.language
[strcspn(key
.language
, ".@")] = 0;
524 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
528 e
= strchr(key
.language
, '_');
531 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
538 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
544 return (const char*) p
+
545 le64toh(h
->header_size
) +
546 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
550 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
551 _cleanup_close_
int fd
= -1;
560 r
= open_mmap(database
, &fd
, &st
, &p
);
581 munmap(p
, st
.st_size
);
586 static char *find_header(const char *s
, const char *header
) {
591 v
= startswith(s
, header
);
593 v
+= strspn(v
, WHITESPACE
);
594 return strndup(v
, strcspn(v
, NEWLINE
));
610 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
612 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
614 subject
= find_header(s
, "Subject:");
615 defined_by
= find_header(s
, "Defined-By:");
617 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
618 SD_ID128_FORMAT_VAL(id
),
619 strna(defined_by
), strna(subject
));
621 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
622 SD_ID128_FORMAT_VAL(id
), s
);
626 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
627 _cleanup_close_
int fd
= -1;
630 const CatalogHeader
*h
;
631 const CatalogItem
*items
;
635 bool last_id_set
= false;
637 r
= open_mmap(database
, &fd
, &st
, &p
);
642 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
644 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
647 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
650 assert_se(s
= find_id(p
, items
[n
].id
));
652 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
655 last_id
= items
[n
].id
;
658 munmap(p
, st
.st_size
);
663 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
667 STRV_FOREACH(item
, items
) {
670 _cleanup_free_
char *msg
= NULL
;
672 k
= sd_id128_from_string(*item
, &id
);
674 log_error_errno(k
, "Failed to parse id128 '%s': %m", *item
);
680 k
= catalog_get(database
, id
, &msg
);
682 log_full_errno(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
, k
,
683 "Failed to retrieve catalog entry for '%s': %m", *item
);
689 dump_catalog_entry(f
, id
, msg
, oneline
);