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(r
, "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 return log_error_errno(errno
, "Failed to read file %s: %m", path
);
236 if (strchr(COMMENTS
"\n", line
[0]))
240 strlen(line
) >= 2+1+32 &&
244 (line
[2+1+32] == ' ' || line
[2+1+32] == '\0')) {
251 with_language
= line
[2+1+32] != '\0';
254 if (sd_id128_from_string(line
+ 2 + 1, &jd
) >= 0) {
257 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
265 t
= strstrip(line
+ 2 + 1 + 32 + 1);
267 r
= catalog_entry_lang(path
, n
, t
, deflang
, &lang
);
285 log_error("[%s:%u] Got payload before ID.", path
, n
);
289 a
= payload
? strlen(payload
) : 0;
292 c
= a
+ (empty_line
? 1 : 0) + b
+ 1 + 1;
293 t
= realloc(payload
, c
);
299 memcpy(t
+ a
+ 1, line
, b
);
303 memcpy(t
+ a
, line
, b
);
313 r
= finish_item(h
, sb
, id
, lang
?: deflang
, payload
);
321 static int64_t write_catalog(const char *database
, struct strbuf
*sb
,
322 CatalogItem
*items
, size_t n
) {
323 CatalogHeader header
;
324 _cleanup_fclose_
FILE *w
= NULL
;
326 _cleanup_free_
char *d
, *p
= NULL
;
329 d
= dirname_malloc(database
);
333 r
= mkdir_p(d
, 0775);
335 return log_error_errno(r
, "Recursive mkdir %s: %m", d
);
337 r
= fopen_temporary(database
, &w
, &p
);
339 return log_error_errno(r
, "Failed to open database for writing: %s: %m",
343 memcpy(header
.signature
, CATALOG_SIGNATURE
, sizeof(header
.signature
));
344 header
.header_size
= htole64(ALIGN_TO(sizeof(CatalogHeader
), 8));
345 header
.catalog_item_size
= htole64(sizeof(CatalogItem
));
346 header
.n_items
= htole64(n
);
350 k
= fwrite(&header
, 1, sizeof(header
), w
);
351 if (k
!= sizeof(header
)) {
352 log_error("%s: failed to write header.", p
);
356 k
= fwrite(items
, 1, n
* sizeof(CatalogItem
), w
);
357 if (k
!= n
* sizeof(CatalogItem
)) {
358 log_error("%s: failed to write database.", p
);
362 k
= fwrite(sb
->buf
, 1, sb
->len
, w
);
364 log_error("%s: failed to write strings.", p
);
368 r
= fflush_and_check(w
);
370 log_error_errno(r
, "%s: failed to write database: %m", p
);
374 fchmod(fileno(w
), 0644);
376 if (rename(p
, database
) < 0) {
377 r
= log_error_errno(errno
, "rename (%s -> %s) failed: %m", p
, database
);
388 int catalog_update(const char* database
, const char* root
, const char* const* dirs
) {
389 _cleanup_strv_free_
char **files
= NULL
;
391 struct strbuf
*sb
= NULL
;
392 _cleanup_hashmap_free_free_ Hashmap
*h
= NULL
;
393 _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 sz
= write_catalog(database
, sb
, items
, n
);
450 r
= log_error_errno(sz
, "Failed to write %s: %m", database
);
453 log_debug("%s: wrote %u items, with %zu bytes of strings, %"PRIi64
" total size.",
454 database
, n
, sb
->len
, sz
);
463 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
464 const CatalogHeader
*h
;
473 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
477 if (fstat(fd
, &st
) < 0) {
482 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
487 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
488 if (p
== MAP_FAILED
) {
494 if (memcmp(h
->signature
, CATALOG_SIGNATURE
, sizeof(h
->signature
)) != 0 ||
495 le64toh(h
->header_size
) < sizeof(CatalogHeader
) ||
496 le64toh(h
->catalog_item_size
) < sizeof(CatalogItem
) ||
497 h
->incompatible_flags
!= 0 ||
498 le64toh(h
->n_items
) <= 0 ||
499 st
.st_size
< (off_t
) (le64toh(h
->header_size
) + le64toh(h
->catalog_item_size
) * le64toh(h
->n_items
))) {
501 munmap(p
, st
.st_size
);
512 static const char *find_id(void *p
, sd_id128_t id
) {
513 CatalogItem key
, *f
= NULL
;
514 const CatalogHeader
*h
= p
;
520 loc
= setlocale(LC_MESSAGES
, NULL
);
521 if (loc
&& loc
[0] && !streq(loc
, "C") && !streq(loc
, "POSIX")) {
522 strncpy(key
.language
, loc
, sizeof(key
.language
));
523 key
.language
[strcspn(key
.language
, ".@")] = 0;
525 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
529 e
= strchr(key
.language
, '_');
532 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
539 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
545 return (const char*) p
+
546 le64toh(h
->header_size
) +
547 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
551 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
552 _cleanup_close_
int fd
= -1;
561 r
= open_mmap(database
, &fd
, &st
, &p
);
582 munmap(p
, st
.st_size
);
587 static char *find_header(const char *s
, const char *header
) {
592 v
= startswith(s
, header
);
594 v
+= strspn(v
, WHITESPACE
);
595 return strndup(v
, strcspn(v
, NEWLINE
));
611 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
613 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
615 subject
= find_header(s
, "Subject:");
616 defined_by
= find_header(s
, "Defined-By:");
618 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
619 SD_ID128_FORMAT_VAL(id
),
620 strna(defined_by
), strna(subject
));
622 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
623 SD_ID128_FORMAT_VAL(id
), s
);
627 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
628 _cleanup_close_
int fd
= -1;
631 const CatalogHeader
*h
;
632 const CatalogItem
*items
;
636 bool last_id_set
= false;
638 r
= open_mmap(database
, &fd
, &st
, &p
);
643 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
645 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
648 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
651 assert_se(s
= find_id(p
, items
[n
].id
));
653 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
656 last_id
= items
[n
].id
;
659 munmap(p
, st
.st_size
);
664 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
668 STRV_FOREACH(item
, items
) {
671 _cleanup_free_
char *msg
= NULL
;
673 k
= sd_id128_from_string(*item
, &id
);
675 log_error_errno(k
, "Failed to parse id128 '%s': %m", *item
);
681 k
= catalog_get(database
, id
, &msg
);
683 log_full_errno(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
, k
,
684 "Failed to retrieve catalog entry for '%s': %m", *item
);
690 dump_catalog_entry(f
, id
, msg
, oneline
);