2 This file is part of systemd.
4 Copyright 2012 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
30 #include "alloc-util.h"
32 #include "conf-files.h"
38 #include "path-util.h"
39 #include "siphash24.h"
40 #include "sparse-endian.h"
42 #include "string-util.h"
46 const char * const catalog_file_dirs
[] = {
47 "/usr/local/lib/systemd/catalog/",
48 "/usr/lib/systemd/catalog/",
52 #define CATALOG_SIGNATURE (uint8_t[]) { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' }
54 typedef struct CatalogHeader
{
55 uint8_t signature
[8]; /* "RHHHKSLP" */
56 le32_t compatible_flags
;
57 le32_t incompatible_flags
;
60 le64_t catalog_item_size
;
63 typedef struct CatalogItem
{
69 static void catalog_hash_func(const void *p
, struct siphash
*state
) {
70 const CatalogItem
*i
= p
;
72 siphash24_compress(&i
->id
, sizeof(i
->id
), state
);
73 siphash24_compress(i
->language
, strlen(i
->language
), state
);
76 static int catalog_compare_func(const void *a
, const void *b
) {
77 const CatalogItem
*i
= a
, *j
= b
;
80 for (k
= 0; k
< ELEMENTSOF(j
->id
.bytes
); k
++) {
81 if (i
->id
.bytes
[k
] < j
->id
.bytes
[k
])
83 if (i
->id
.bytes
[k
] > j
->id
.bytes
[k
])
87 return strcmp(i
->language
, j
->language
);
90 const struct hash_ops catalog_hash_ops
= {
91 .hash
= catalog_hash_func
,
92 .compare
= catalog_compare_func
95 static bool next_header(const char **s
) {
112 static const char *skip_header(const char *s
) {
113 while (next_header(&s
))
118 static char *combine_entries(const char *one
, const char *two
) {
123 /* Find split point of headers to body */
124 b1
= skip_header(one
);
125 b2
= skip_header(two
);
129 dest
= new(char, l1
+ l2
+ 1);
137 /* Headers from @one */
139 p
= mempcpy(p
, one
, n
);
141 /* Headers from @two, these will only be found if not present above */
143 p
= mempcpy(p
, two
, n
);
158 assert(p
- dest
<= (ptrdiff_t)(l1
+ l2
));
163 static int finish_item(
166 const char *language
,
167 char *payload
, size_t payload_size
) {
169 _cleanup_free_ CatalogItem
*i
= NULL
;
170 _cleanup_free_
char *prev
= NULL
, *combined
= NULL
;
174 assert(payload_size
> 0);
176 i
= new0(CatalogItem
, 1);
182 assert(strlen(language
) > 1 && strlen(language
) < 32);
183 strcpy(i
->language
, language
);
186 prev
= hashmap_get(h
, i
);
188 /* Already have such an item, combine them */
189 combined
= combine_entries(payload
, prev
);
193 if (hashmap_update(h
, i
, combined
) < 0)
198 combined
= memdup(payload
, payload_size
+ 1);
202 if (hashmap_put(h
, i
, combined
) < 0)
211 int catalog_file_lang(const char* filename
, char **lang
) {
212 char *beg
, *end
, *_lang
;
214 end
= endswith(filename
, ".catalog");
219 while (beg
> filename
&& *beg
!= '.' && *beg
!= '/' && end
- beg
< 32)
222 if (*beg
!= '.' || end
<= beg
+ 1)
225 _lang
= strndup(beg
+ 1, end
- beg
- 1);
233 static int catalog_entry_lang(const char* filename
, int line
,
234 const char* t
, const char* deflang
, char **lang
) {
239 log_error("[%s:%u] Language too short.", filename
, line
);
243 log_error("[%s:%u] language too long.", filename
, line
);
248 if (streq(t
, deflang
)) {
249 log_warning("[%s:%u] language specified unnecessarily",
253 log_warning("[%s:%u] language differs from default for file",
264 int catalog_import_file(Hashmap
*h
, const char *path
) {
265 _cleanup_fclose_
FILE *f
= NULL
;
266 _cleanup_free_
char *payload
= NULL
;
267 size_t payload_size
= 0, payload_allocated
= 0;
270 _cleanup_free_
char *deflang
= NULL
, *lang
= NULL
;
271 bool got_id
= false, empty_line
= true;
277 f
= fopen(path
, "re");
279 return log_error_errno(errno
, "Failed to open file %s: %m", path
);
281 r
= catalog_file_lang(path
, &deflang
);
283 log_error_errno(r
, "Failed to determine language for file %s: %m", path
);
285 log_debug("File %s has language %s.", path
, deflang
);
291 if (!fgets(line
, sizeof(line
), f
)) {
295 return log_error_errno(errno
, "Failed to read file %s: %m", path
);
307 if (strchr(COMMENTS
"\n", line
[0]))
311 strlen(line
) >= 2+1+32 &&
315 (line
[2+1+32] == ' ' || line
[2+1+32] == '\0')) {
322 with_language
= line
[2+1+32] != '\0';
325 if (sd_id128_from_string(line
+ 2 + 1, &jd
) >= 0) {
328 if (payload_size
== 0) {
329 log_error("[%s:%u] No payload text.", path
, n
);
333 r
= finish_item(h
, id
, lang
?: deflang
, payload
, payload_size
);
344 t
= strstrip(line
+ 2 + 1 + 32 + 1);
345 r
= catalog_entry_lang(path
, n
, t
, deflang
, &lang
);
360 log_error("[%s:%u] Got payload before ID.", path
, n
);
364 line_len
= strlen(line
);
365 if (!GREEDY_REALLOC(payload
, payload_allocated
,
366 payload_size
+ (empty_line
? 1 : 0) + line_len
+ 1 + 1))
370 payload
[payload_size
++] = '\n';
371 memcpy(payload
+ payload_size
, line
, line_len
);
372 payload_size
+= line_len
;
373 payload
[payload_size
++] = '\n';
374 payload
[payload_size
] = '\0';
380 if (payload_size
== 0) {
381 log_error("[%s:%u] No payload text.", path
, n
);
385 r
= finish_item(h
, id
, lang
?: deflang
, payload
, payload_size
);
393 static int64_t write_catalog(const char *database
, struct strbuf
*sb
,
394 CatalogItem
*items
, size_t n
) {
395 CatalogHeader header
;
396 _cleanup_fclose_
FILE *w
= NULL
;
398 _cleanup_free_
char *d
, *p
= NULL
;
401 d
= dirname_malloc(database
);
405 r
= mkdir_p(d
, 0775);
407 return log_error_errno(r
, "Recursive mkdir %s: %m", d
);
409 r
= fopen_temporary(database
, &w
, &p
);
411 return log_error_errno(r
, "Failed to open database for writing: %s: %m",
415 memcpy(header
.signature
, CATALOG_SIGNATURE
, sizeof(header
.signature
));
416 header
.header_size
= htole64(ALIGN_TO(sizeof(CatalogHeader
), 8));
417 header
.catalog_item_size
= htole64(sizeof(CatalogItem
));
418 header
.n_items
= htole64(n
);
422 k
= fwrite(&header
, 1, sizeof(header
), w
);
423 if (k
!= sizeof(header
)) {
424 log_error("%s: failed to write header.", p
);
428 k
= fwrite(items
, 1, n
* sizeof(CatalogItem
), w
);
429 if (k
!= n
* sizeof(CatalogItem
)) {
430 log_error("%s: failed to write database.", p
);
434 k
= fwrite(sb
->buf
, 1, sb
->len
, w
);
436 log_error("%s: failed to write strings.", p
);
440 r
= fflush_and_check(w
);
442 log_error_errno(r
, "%s: failed to write database: %m", p
);
446 fchmod(fileno(w
), 0644);
448 if (rename(p
, database
) < 0) {
449 r
= log_error_errno(errno
, "rename (%s -> %s) failed: %m", p
, database
);
460 int catalog_update(const char* database
, const char* root
, const char* const* dirs
) {
461 _cleanup_strv_free_
char **files
= NULL
;
463 struct strbuf
*sb
= NULL
;
464 _cleanup_hashmap_free_free_free_ Hashmap
*h
= NULL
;
465 _cleanup_free_ CatalogItem
*items
= NULL
;
474 h
= hashmap_new(&catalog_hash_ops
);
482 r
= conf_files_list_strv(&files
, ".catalog", root
, 0, dirs
);
484 log_error_errno(r
, "Failed to get catalog files: %m");
488 STRV_FOREACH(f
, files
) {
489 log_debug("Reading file '%s'", *f
);
490 r
= catalog_import_file(h
, *f
);
492 log_error_errno(r
, "Failed to import file '%s': %m", *f
);
497 if (hashmap_size(h
) <= 0) {
498 log_info("No items in catalog.");
501 log_debug("Found %u items in catalog.", hashmap_size(h
));
503 items
= new(CatalogItem
, hashmap_size(h
));
510 HASHMAP_FOREACH_KEY(payload
, i
, h
, j
) {
511 log_debug("Found " SD_ID128_FORMAT_STR
", language %s",
512 SD_ID128_FORMAT_VAL(i
->id
),
513 isempty(i
->language
) ? "C" : i
->language
);
515 offset
= strbuf_add_string(sb
, payload
, strlen(payload
));
520 i
->offset
= htole64((uint64_t) offset
);
524 assert(n
== hashmap_size(h
));
525 qsort_safe(items
, n
, sizeof(CatalogItem
), catalog_compare_func
);
529 sz
= write_catalog(database
, sb
, items
, n
);
531 r
= log_error_errno(sz
, "Failed to write %s: %m", database
);
534 log_debug("%s: wrote %u items, with %zu bytes of strings, %"PRIi64
" total size.",
535 database
, n
, sb
->len
, sz
);
544 static int open_mmap(const char *database
, int *_fd
, struct stat
*_st
, void **_p
) {
545 const CatalogHeader
*h
;
554 fd
= open(database
, O_RDONLY
|O_CLOEXEC
);
558 if (fstat(fd
, &st
) < 0) {
563 if (st
.st_size
< (off_t
) sizeof(CatalogHeader
)) {
568 p
= mmap(NULL
, PAGE_ALIGN(st
.st_size
), PROT_READ
, MAP_SHARED
, fd
, 0);
569 if (p
== MAP_FAILED
) {
575 if (memcmp(h
->signature
, CATALOG_SIGNATURE
, sizeof(h
->signature
)) != 0 ||
576 le64toh(h
->header_size
) < sizeof(CatalogHeader
) ||
577 le64toh(h
->catalog_item_size
) < sizeof(CatalogItem
) ||
578 h
->incompatible_flags
!= 0 ||
579 le64toh(h
->n_items
) <= 0 ||
580 st
.st_size
< (off_t
) (le64toh(h
->header_size
) + le64toh(h
->catalog_item_size
) * le64toh(h
->n_items
))) {
582 munmap(p
, st
.st_size
);
593 static const char *find_id(void *p
, sd_id128_t id
) {
594 CatalogItem key
, *f
= NULL
;
595 const CatalogHeader
*h
= p
;
601 loc
= setlocale(LC_MESSAGES
, NULL
);
602 if (loc
&& loc
[0] && !streq(loc
, "C") && !streq(loc
, "POSIX")) {
603 strncpy(key
.language
, loc
, sizeof(key
.language
));
604 key
.language
[strcspn(key
.language
, ".@")] = 0;
606 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
610 e
= strchr(key
.language
, '_');
613 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
620 f
= bsearch(&key
, (const uint8_t*) p
+ le64toh(h
->header_size
), le64toh(h
->n_items
), le64toh(h
->catalog_item_size
), catalog_compare_func
);
626 return (const char*) p
+
627 le64toh(h
->header_size
) +
628 le64toh(h
->n_items
) * le64toh(h
->catalog_item_size
) +
632 int catalog_get(const char* database
, sd_id128_t id
, char **_text
) {
633 _cleanup_close_
int fd
= -1;
642 r
= open_mmap(database
, &fd
, &st
, &p
);
663 munmap(p
, st
.st_size
);
668 static char *find_header(const char *s
, const char *header
) {
673 v
= startswith(s
, header
);
675 v
+= strspn(v
, WHITESPACE
);
676 return strndup(v
, strcspn(v
, NEWLINE
));
679 if (!next_header(&s
))
684 static void dump_catalog_entry(FILE *f
, sd_id128_t id
, const char *s
, bool oneline
) {
686 _cleanup_free_
char *subject
= NULL
, *defined_by
= NULL
;
688 subject
= find_header(s
, "Subject:");
689 defined_by
= find_header(s
, "Defined-By:");
691 fprintf(f
, SD_ID128_FORMAT_STR
" %s: %s\n",
692 SD_ID128_FORMAT_VAL(id
),
693 strna(defined_by
), strna(subject
));
695 fprintf(f
, "-- " SD_ID128_FORMAT_STR
"\n%s\n",
696 SD_ID128_FORMAT_VAL(id
), s
);
700 int catalog_list(FILE *f
, const char *database
, bool oneline
) {
701 _cleanup_close_
int fd
= -1;
704 const CatalogHeader
*h
;
705 const CatalogItem
*items
;
709 bool last_id_set
= false;
711 r
= open_mmap(database
, &fd
, &st
, &p
);
716 items
= (const CatalogItem
*) ((const uint8_t*) p
+ le64toh(h
->header_size
));
718 for (n
= 0; n
< le64toh(h
->n_items
); n
++) {
721 if (last_id_set
&& sd_id128_equal(last_id
, items
[n
].id
))
724 assert_se(s
= find_id(p
, items
[n
].id
));
726 dump_catalog_entry(f
, items
[n
].id
, s
, oneline
);
729 last_id
= items
[n
].id
;
732 munmap(p
, st
.st_size
);
737 int catalog_list_items(FILE *f
, const char *database
, bool oneline
, char **items
) {
741 STRV_FOREACH(item
, items
) {
744 _cleanup_free_
char *msg
= NULL
;
746 k
= sd_id128_from_string(*item
, &id
);
748 log_error_errno(k
, "Failed to parse id128 '%s': %m", *item
);
754 k
= catalog_get(database
, id
, &msg
);
756 log_full_errno(k
== -ENOENT
? LOG_NOTICE
: LOG_ERR
, k
,
757 "Failed to retrieve catalog entry for '%s': %m", *item
);
763 dump_catalog_entry(f
, id
, msg
, oneline
);