]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed-util.c
fileio: add new helper fdopen_independent()
[thirdparty/systemd.git] / src / locale / localed-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <sys/stat.h>
5 #include <sys/types.h>
6 #include <unistd.h>
7
8 #include "bus-polkit.h"
9 #include "copy.h"
10 #include "env-file-label.h"
11 #include "env-file.h"
12 #include "env-util.h"
13 #include "fd-util.h"
14 #include "fileio-label.h"
15 #include "fileio.h"
16 #include "fs-util.h"
17 #include "kbd-util.h"
18 #include "localed-util.h"
19 #include "macro.h"
20 #include "mkdir-label.h"
21 #include "nulstr-util.h"
22 #include "process-util.h"
23 #include "stat-util.h"
24 #include "string-util.h"
25 #include "strv.h"
26 #include "tmpfile-util.h"
27
28 static bool startswith_comma(const char *s, const char *prefix) {
29 assert(s);
30 assert(prefix);
31
32 s = startswith(s, prefix);
33 if (!s)
34 return false;
35
36 return IN_SET(*s, ',', '\0');
37 }
38
39 static const char* systemd_kbd_model_map(void) {
40 const char* s;
41
42 s = getenv("SYSTEMD_KBD_MODEL_MAP");
43 if (s)
44 return s;
45
46 return SYSTEMD_KBD_MODEL_MAP;
47 }
48
49 static const char* systemd_language_fallback_map(void) {
50 const char* s;
51
52 s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
53 if (s)
54 return s;
55
56 return SYSTEMD_LANGUAGE_FALLBACK_MAP;
57 }
58
59 void x11_context_clear(X11Context *xc) {
60 assert(xc);
61
62 xc->layout = mfree(xc->layout);
63 xc->options = mfree(xc->options);
64 xc->model = mfree(xc->model);
65 xc->variant = mfree(xc->variant);
66 }
67
68 void x11_context_replace(X11Context *dest, X11Context *src) {
69 assert(dest);
70 assert(src);
71
72 x11_context_clear(dest);
73 *dest = *src;
74 *src = (X11Context) {};
75 }
76
77 bool x11_context_isempty(const X11Context *xc) {
78 assert(xc);
79
80 return
81 isempty(xc->layout) &&
82 isempty(xc->model) &&
83 isempty(xc->variant) &&
84 isempty(xc->options);
85 }
86
87 void x11_context_empty_to_null(X11Context *xc) {
88 assert(xc);
89
90 /* Do not call x11_context_clear() for the passed object. */
91
92 xc->layout = empty_to_null(xc->layout);
93 xc->model = empty_to_null(xc->model);
94 xc->variant = empty_to_null(xc->variant);
95 xc->options = empty_to_null(xc->options);
96 }
97
98 bool x11_context_is_safe(const X11Context *xc) {
99 assert(xc);
100
101 return
102 (!xc->layout || string_is_safe(xc->layout)) &&
103 (!xc->model || string_is_safe(xc->model)) &&
104 (!xc->variant || string_is_safe(xc->variant)) &&
105 (!xc->options || string_is_safe(xc->options));
106 }
107
108 bool x11_context_equal(const X11Context *a, const X11Context *b) {
109 assert(a);
110 assert(b);
111
112 return
113 streq_ptr(a->layout, b->layout) &&
114 streq_ptr(a->model, b->model) &&
115 streq_ptr(a->variant, b->variant) &&
116 streq_ptr(a->options, b->options);
117 }
118
119 int x11_context_copy(X11Context *dest, const X11Context *src) {
120 bool modified;
121 int r;
122
123 assert(dest);
124
125 if (dest == src)
126 return 0;
127
128 if (!src) {
129 modified = !x11_context_isempty(dest);
130 x11_context_clear(dest);
131 return modified;
132 }
133
134 r = free_and_strdup(&dest->layout, src->layout);
135 if (r < 0)
136 return r;
137 modified = r > 0;
138
139 r = free_and_strdup(&dest->model, src->model);
140 if (r < 0)
141 return r;
142 modified = modified || r > 0;
143
144 r = free_and_strdup(&dest->variant, src->variant);
145 if (r < 0)
146 return r;
147 modified = modified || r > 0;
148
149 r = free_and_strdup(&dest->options, src->options);
150 if (r < 0)
151 return r;
152 modified = modified || r > 0;
153
154 return modified;
155 }
156
157 void vc_context_clear(VCContext *vc) {
158 assert(vc);
159
160 vc->keymap = mfree(vc->keymap);
161 vc->toggle = mfree(vc->toggle);
162 }
163
164 void vc_context_replace(VCContext *dest, VCContext *src) {
165 assert(dest);
166 assert(src);
167
168 vc_context_clear(dest);
169 *dest = *src;
170 *src = (VCContext) {};
171 }
172
173 bool vc_context_isempty(const VCContext *vc) {
174 assert(vc);
175
176 return
177 isempty(vc->keymap) &&
178 isempty(vc->toggle);
179 }
180
181 void vc_context_empty_to_null(VCContext *vc) {
182 assert(vc);
183
184 /* Do not call vc_context_clear() for the passed object. */
185
186 vc->keymap = empty_to_null(vc->keymap);
187 vc->toggle = empty_to_null(vc->toggle);
188 }
189
190 bool vc_context_equal(const VCContext *a, const VCContext *b) {
191 assert(a);
192 assert(b);
193
194 return
195 streq_ptr(a->keymap, b->keymap) &&
196 streq_ptr(a->toggle, b->toggle);
197 }
198
199 int vc_context_copy(VCContext *dest, const VCContext *src) {
200 bool modified;
201 int r;
202
203 assert(dest);
204
205 if (dest == src)
206 return 0;
207
208 if (!src) {
209 modified = !vc_context_isempty(dest);
210 vc_context_clear(dest);
211 return modified;
212 }
213
214 r = free_and_strdup(&dest->keymap, src->keymap);
215 if (r < 0)
216 return r;
217 modified = r > 0;
218
219 r = free_and_strdup(&dest->toggle, src->toggle);
220 if (r < 0)
221 return r;
222 modified = modified || r > 0;
223
224 return modified;
225 }
226
227 void context_clear(Context *c) {
228 assert(c);
229
230 locale_context_clear(&c->locale_context);
231 x11_context_clear(&c->x11_from_xorg);
232 x11_context_clear(&c->x11_from_vc);
233 vc_context_clear(&c->vc);
234
235 c->locale_cache = sd_bus_message_unref(c->locale_cache);
236 c->x11_cache = sd_bus_message_unref(c->x11_cache);
237 c->vc_cache = sd_bus_message_unref(c->vc_cache);
238
239 c->polkit_registry = bus_verify_polkit_async_registry_free(c->polkit_registry);
240 };
241
242 X11Context *context_get_x11_context(Context *c) {
243 assert(c);
244
245 if (!x11_context_isempty(&c->x11_from_vc))
246 return &c->x11_from_vc;
247
248 if (!x11_context_isempty(&c->x11_from_xorg))
249 return &c->x11_from_xorg;
250
251 return &c->x11_from_vc;
252 }
253
254 int locale_read_data(Context *c, sd_bus_message *m) {
255 assert(c);
256
257 /* Do not try to re-read the file within single bus operation. */
258 if (m) {
259 if (m == c->locale_cache)
260 return 0;
261
262 sd_bus_message_unref(c->locale_cache);
263 c->locale_cache = sd_bus_message_ref(m);
264 }
265
266 return locale_context_load(&c->locale_context, LOCALE_LOAD_LOCALE_CONF | LOCALE_LOAD_ENVIRONMENT | LOCALE_LOAD_SIMPLIFY);
267 }
268
269 int vconsole_read_data(Context *c, sd_bus_message *m) {
270 _cleanup_close_ int fd = -EBADF;
271 struct stat st;
272
273 assert(c);
274
275 /* Do not try to re-read the file within single bus operation. */
276 if (m) {
277 if (m == c->vc_cache)
278 return 0;
279
280 sd_bus_message_unref(c->vc_cache);
281 c->vc_cache = sd_bus_message_ref(m);
282 }
283
284 fd = RET_NERRNO(open("/etc/vconsole.conf", O_CLOEXEC | O_PATH));
285 if (fd == -ENOENT) {
286 c->vc_stat = (struct stat) {};
287 vc_context_clear(&c->vc);
288 x11_context_clear(&c->x11_from_vc);
289 return 0;
290 }
291 if (fd < 0)
292 return fd;
293
294 if (fstat(fd, &st) < 0)
295 return -errno;
296
297 /* If the file is not changed, then we do not need to re-read */
298 if (stat_inode_unmodified(&c->vc_stat, &st))
299 return 0;
300
301 c->vc_stat = st;
302 vc_context_clear(&c->vc);
303 x11_context_clear(&c->x11_from_vc);
304
305 return parse_env_file_fd(fd, "/etc/vconsole.conf",
306 "KEYMAP", &c->vc.keymap,
307 "KEYMAP_TOGGLE", &c->vc.toggle,
308 "XKBLAYOUT", &c->x11_from_vc.layout,
309 "XKBMODEL", &c->x11_from_vc.model,
310 "XKBVARIANT", &c->x11_from_vc.variant,
311 "XKBOPTIONS", &c->x11_from_vc.options);
312 }
313
314 int x11_read_data(Context *c, sd_bus_message *m) {
315 _cleanup_close_ int fd = -EBADF;
316 _cleanup_fclose_ FILE *f = NULL;
317 bool in_section = false;
318 struct stat st;
319 int r;
320
321 assert(c);
322
323 /* Do not try to re-read the file within single bus operation. */
324 if (m) {
325 if (m == c->x11_cache)
326 return 0;
327
328 sd_bus_message_unref(c->x11_cache);
329 c->x11_cache = sd_bus_message_ref(m);
330 }
331
332 fd = RET_NERRNO(open("/etc/X11/xorg.conf.d/00-keyboard.conf", O_CLOEXEC | O_PATH));
333 if (fd == -ENOENT) {
334 c->x11_stat = (struct stat) {};
335 x11_context_clear(&c->x11_from_xorg);
336 return 0;
337 }
338 if (fd < 0)
339 return fd;
340
341 if (fstat(fd, &st) < 0)
342 return -errno;
343
344 /* If the file is not changed, then we do not need to re-read */
345 if (stat_inode_unmodified(&c->x11_stat, &st))
346 return 0;
347
348 c->x11_stat = st;
349 x11_context_clear(&c->x11_from_xorg);
350
351 r = fdopen_independent(fd, "re", &f);
352 if (r < 0)
353 return r;
354
355 for (;;) {
356 _cleanup_free_ char *line = NULL;
357 char *l;
358
359 r = read_line(f, LONG_LINE_MAX, &line);
360 if (r < 0)
361 return r;
362 if (r == 0)
363 break;
364
365 l = strstrip(line);
366 if (IN_SET(l[0], 0, '#'))
367 continue;
368
369 if (in_section && first_word(l, "Option")) {
370 _cleanup_strv_free_ char **a = NULL;
371
372 r = strv_split_full(&a, l, WHITESPACE, EXTRACT_UNQUOTE);
373 if (r < 0)
374 return r;
375
376 if (strv_length(a) == 3) {
377 char **p = NULL;
378
379 if (streq(a[1], "XkbLayout"))
380 p = &c->x11_from_xorg.layout;
381 else if (streq(a[1], "XkbModel"))
382 p = &c->x11_from_xorg.model;
383 else if (streq(a[1], "XkbVariant"))
384 p = &c->x11_from_xorg.variant;
385 else if (streq(a[1], "XkbOptions"))
386 p = &c->x11_from_xorg.options;
387
388 if (p)
389 free_and_replace(*p, a[2]);
390 }
391
392 } else if (!in_section && first_word(l, "Section")) {
393 _cleanup_strv_free_ char **a = NULL;
394
395 r = strv_split_full(&a, l, WHITESPACE, EXTRACT_UNQUOTE);
396 if (r < 0)
397 return -ENOMEM;
398
399 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
400 in_section = true;
401
402 } else if (in_section && first_word(l, "EndSection"))
403 in_section = false;
404 }
405
406 return 0;
407 }
408
409 int vconsole_write_data(Context *c) {
410 _cleanup_strv_free_ char **l = NULL;
411 const X11Context *xc;
412 int r;
413
414 assert(c);
415
416 xc = context_get_x11_context(c);
417
418 r = load_env_file(NULL, "/etc/vconsole.conf", &l);
419 if (r < 0 && r != -ENOENT)
420 return r;
421
422 r = strv_env_assign(&l, "KEYMAP", empty_to_null(c->vc.keymap));
423 if (r < 0)
424 return r;
425
426 r = strv_env_assign(&l, "KEYMAP_TOGGLE", empty_to_null(c->vc.toggle));
427 if (r < 0)
428 return r;
429
430 r = strv_env_assign(&l, "XKBLAYOUT", empty_to_null(xc->layout));
431 if (r < 0)
432 return r;
433
434 r = strv_env_assign(&l, "XKBMODEL", empty_to_null(xc->model));
435 if (r < 0)
436 return r;
437
438 r = strv_env_assign(&l, "XKBVARIANT", empty_to_null(xc->variant));
439 if (r < 0)
440 return r;
441
442 r = strv_env_assign(&l, "XKBOPTIONS", empty_to_null(xc->options));
443 if (r < 0)
444 return r;
445
446 if (strv_isempty(l)) {
447 if (unlink("/etc/vconsole.conf") < 0)
448 return errno == ENOENT ? 0 : -errno;
449
450 c->vc_stat = (struct stat) {};
451 return 0;
452 }
453
454 r = write_env_file_label("/etc/vconsole.conf", l);
455 if (r < 0)
456 return r;
457
458 if (stat("/etc/vconsole.conf", &c->vc_stat) < 0)
459 return -errno;
460
461 return 0;
462 }
463
464 int x11_write_data(Context *c) {
465 _cleanup_fclose_ FILE *f = NULL;
466 _cleanup_(unlink_and_freep) char *temp_path = NULL;
467 const X11Context *xc;
468 int r;
469
470 assert(c);
471
472 xc = context_get_x11_context(c);
473 if (x11_context_isempty(xc)) {
474 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
475 return errno == ENOENT ? 0 : -errno;
476
477 c->x11_stat = (struct stat) {};
478 return 0;
479 }
480
481 (void) mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
482 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
483 if (r < 0)
484 return r;
485
486 (void) fchmod(fileno(f), 0644);
487
488 fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
489 "# probably wise not to edit this file manually. Use localectl(1) to\n"
490 "# instruct systemd-localed to update it.\n"
491 "Section \"InputClass\"\n"
492 " Identifier \"system-keyboard\"\n"
493 " MatchIsKeyboard \"on\"\n", f);
494
495 if (!isempty(xc->layout))
496 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", xc->layout);
497
498 if (!isempty(xc->model))
499 fprintf(f, " Option \"XkbModel\" \"%s\"\n", xc->model);
500
501 if (!isempty(xc->variant))
502 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", xc->variant);
503
504 if (!isempty(xc->options))
505 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", xc->options);
506
507 fputs("EndSection\n", f);
508
509 r = fflush_sync_and_check(f);
510 if (r < 0)
511 return r;
512
513 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
514 return -errno;
515
516 if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &c->x11_stat) < 0)
517 return -errno;
518
519 return 0;
520 }
521
522 static int read_next_mapping(
523 const char *filename,
524 unsigned min_fields,
525 unsigned max_fields,
526 FILE *f,
527 unsigned *n,
528 char ***ret) {
529
530 assert(f);
531 assert(n);
532 assert(ret);
533
534 for (;;) {
535 _cleanup_strv_free_ char **b = NULL;
536 _cleanup_free_ char *line = NULL;
537 size_t length;
538 const char *l;
539 int r;
540
541 r = read_line(f, LONG_LINE_MAX, &line);
542 if (r < 0)
543 return r;
544 if (r == 0)
545 break;
546
547 (*n)++;
548
549 l = strstrip(line);
550 if (IN_SET(l[0], 0, '#'))
551 continue;
552
553 r = strv_split_full(&b, l, WHITESPACE, EXTRACT_UNQUOTE);
554 if (r < 0)
555 return r;
556
557 length = strv_length(b);
558 if (length < min_fields || length > max_fields) {
559 log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n);
560 continue;
561
562 }
563
564 *ret = TAKE_PTR(b);
565 return 1;
566 }
567
568 *ret = NULL;
569 return 0;
570 }
571
572 int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) {
573 _cleanup_fclose_ FILE *f = NULL;
574 const char *map;
575 int r;
576
577 assert(vc);
578 assert(ret);
579
580 if (isempty(vc->keymap)) {
581 *ret = (X11Context) {};
582 return 0;
583 }
584
585 map = systemd_kbd_model_map();
586 f = fopen(map, "re");
587 if (!f)
588 return -errno;
589
590 for (unsigned n = 0;;) {
591 _cleanup_strv_free_ char **a = NULL;
592
593 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
594 if (r < 0)
595 return r;
596 if (r == 0) {
597 *ret = (X11Context) {};
598 return 0;
599 }
600
601 if (!streq(vc->keymap, a[0]))
602 continue;
603
604 return x11_context_copy(ret,
605 &(X11Context) {
606 .layout = empty_or_dash_to_null(a[1]),
607 .model = empty_or_dash_to_null(a[2]),
608 .variant = empty_or_dash_to_null(a[3]),
609 .options = empty_or_dash_to_null(a[4]),
610 });
611 }
612 }
613
614 int find_converted_keymap(const X11Context *xc, char **ret) {
615 _cleanup_free_ char *n = NULL;
616
617 assert(xc);
618 assert(!isempty(xc->layout));
619 assert(ret);
620
621 if (xc->variant)
622 n = strjoin(xc->layout, "-", xc->variant);
623 else
624 n = strdup(xc->layout);
625 if (!n)
626 return -ENOMEM;
627
628 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
629 _cleanup_free_ char *p = NULL, *pz = NULL;
630 bool uncompressed;
631
632 p = strjoin(dir, "xkb/", n, ".map");
633 pz = strjoin(dir, "xkb/", n, ".map.gz");
634 if (!p || !pz)
635 return -ENOMEM;
636
637 uncompressed = access(p, F_OK) == 0;
638 if (uncompressed || access(pz, F_OK) == 0) {
639 log_debug("Found converted keymap %s at %s", n, uncompressed ? p : pz);
640 *ret = TAKE_PTR(n);
641 return 1;
642 }
643 }
644
645 *ret = NULL;
646 return 0;
647 }
648
649 int find_legacy_keymap(const X11Context *xc, char **ret) {
650 const char *map;
651 _cleanup_fclose_ FILE *f = NULL;
652 _cleanup_free_ char *new_keymap = NULL;
653 unsigned best_matching = 0;
654 int r;
655
656 assert(xc);
657 assert(!isempty(xc->layout));
658
659 map = systemd_kbd_model_map();
660 f = fopen(map, "re");
661 if (!f)
662 return -errno;
663
664 for (unsigned n = 0;;) {
665 _cleanup_strv_free_ char **a = NULL;
666 unsigned matching = 0;
667
668 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
669 if (r < 0)
670 return r;
671 if (r == 0)
672 break;
673
674 /* Determine how well matching this entry is */
675 if (streq(xc->layout, a[1]))
676 /* If we got an exact match, this is the best */
677 matching = 10;
678 else {
679 /* We have multiple X layouts, look for an
680 * entry that matches our key with everything
681 * but the first layout stripped off. */
682 if (startswith_comma(xc->layout, a[1]))
683 matching = 5;
684 else {
685 _cleanup_free_ char *x = NULL;
686
687 /* If that didn't work, strip off the
688 * other layouts from the entry, too */
689 x = strdupcspn(a[1], ",");
690 if (!x)
691 return -ENOMEM;
692 if (startswith_comma(xc->layout, x))
693 matching = 1;
694 }
695 }
696
697 if (matching > 0) {
698 if (isempty(xc->model) || streq_ptr(xc->model, a[2])) {
699 matching++;
700
701 if (streq_ptr(xc->variant, a[3])) {
702 matching++;
703
704 if (streq_ptr(xc->options, a[4]))
705 matching++;
706 }
707 }
708 }
709
710 /* The best matching entry so far, then let's save that */
711 if (matching >= MAX(best_matching, 1u)) {
712 log_debug("Found legacy keymap %s with score %u", a[0], matching);
713
714 if (matching > best_matching) {
715 best_matching = matching;
716
717 r = free_and_strdup(&new_keymap, a[0]);
718 if (r < 0)
719 return r;
720 }
721 }
722 }
723
724 if (best_matching < 10 && !isempty(xc->layout)) {
725 _cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL;
726
727 /* The best match is only the first part of the X11
728 * keymap. Check if we have a converted map which
729 * matches just the first layout.
730 */
731
732 l = strndup(xc->layout, strcspn(xc->layout, ","));
733 if (!l)
734 return -ENOMEM;
735
736 if (!isempty(xc->variant)) {
737 v = strndup(xc->variant, strcspn(xc->variant, ","));
738 if (!v)
739 return -ENOMEM;
740 }
741
742 r = find_converted_keymap(
743 &(X11Context) {
744 .layout = l,
745 .variant = v,
746 },
747 &converted);
748 if (r < 0)
749 return r;
750 if (r > 0)
751 free_and_replace(new_keymap, converted);
752 }
753
754 *ret = TAKE_PTR(new_keymap);
755 return !!*ret;
756 }
757
758 int find_language_fallback(const char *lang, char **ret) {
759 const char *map;
760 _cleanup_fclose_ FILE *f = NULL;
761 unsigned n = 0;
762 int r;
763
764 assert(lang);
765 assert(ret);
766
767 map = systemd_language_fallback_map();
768 f = fopen(map, "re");
769 if (!f)
770 return -errno;
771
772 for (;;) {
773 _cleanup_strv_free_ char **a = NULL;
774
775 r = read_next_mapping(map, 2, 2, f, &n, &a);
776 if (r <= 0)
777 return r;
778
779 if (streq(lang, a[0])) {
780 assert(strv_length(a) == 2);
781 *ret = TAKE_PTR(a[1]);
782 return 1;
783 }
784 }
785 }
786
787 int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
788 _cleanup_free_ char *keymap = NULL;
789 int r;
790
791 assert(xc);
792 assert(ret);
793
794 if (isempty(xc->layout)) {
795 *ret = (VCContext) {};
796 return 0;
797 }
798
799 r = find_converted_keymap(xc, &keymap);
800 if (r == 0)
801 r = find_legacy_keymap(xc, &keymap);
802 if (r < 0)
803 return r;
804
805 *ret = (VCContext) {
806 .keymap = TAKE_PTR(keymap),
807 };
808 return 0;
809 }
810
811 bool locale_gen_check_available(void) {
812 #if HAVE_LOCALEGEN
813 if (access(LOCALEGEN_PATH, X_OK) < 0) {
814 if (errno != ENOENT)
815 log_warning_errno(errno, "Unable to determine whether " LOCALEGEN_PATH " exists and is executable, assuming it is not: %m");
816 return false;
817 }
818 if (access("/etc/locale.gen", F_OK) < 0) {
819 if (errno != ENOENT)
820 log_warning_errno(errno, "Unable to determine whether /etc/locale.gen exists, assuming it does not: %m");
821 return false;
822 }
823 return true;
824 #else
825 return false;
826 #endif
827 }
828
829 #if HAVE_LOCALEGEN
830 static bool locale_encoding_is_utf8_or_unspecified(const char *locale) {
831 const char *c = strchr(locale, '.');
832 return !c || strcaseeq(c, ".UTF-8") || strcasestr(locale, ".UTF-8@");
833 }
834
835 static int locale_gen_locale_supported(const char *locale_entry) {
836 /* Returns an error valus <= 0 if the locale-gen entry is invalid or unsupported,
837 * 1 in case the locale entry is valid, and -EOPNOTSUPP specifically in case
838 * the distributor has not provided us with a SUPPORTED file to check
839 * locale for validity. */
840
841 _cleanup_fclose_ FILE *f = NULL;
842 int r;
843
844 assert(locale_entry);
845
846 /* Locale templates without country code are never supported */
847 if (!strstr(locale_entry, "_"))
848 return -EINVAL;
849
850 f = fopen("/usr/share/i18n/SUPPORTED", "re");
851 if (!f) {
852 if (errno == ENOENT)
853 return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
854 "Unable to check validity of locale entry %s: /usr/share/i18n/SUPPORTED does not exist",
855 locale_entry);
856 return -errno;
857 }
858
859 for (;;) {
860 _cleanup_free_ char *line = NULL;
861
862 r = read_line(f, LONG_LINE_MAX, &line);
863 if (r < 0)
864 return log_debug_errno(r, "Failed to read /usr/share/i18n/SUPPORTED: %m");
865 if (r == 0)
866 return 0;
867
868 line = strstrip(line);
869 if (strcaseeq_ptr(line, locale_entry))
870 return 1;
871 }
872 }
873 #endif
874
875 int locale_gen_enable_locale(const char *locale) {
876 #if HAVE_LOCALEGEN
877 _cleanup_fclose_ FILE *fr = NULL, *fw = NULL;
878 _cleanup_(unlink_and_freep) char *temp_path = NULL;
879 _cleanup_free_ char *locale_entry = NULL;
880 bool locale_enabled = false, first_line = false;
881 bool write_new = false;
882 int r;
883
884 if (isempty(locale))
885 return 0;
886
887 if (locale_encoding_is_utf8_or_unspecified(locale)) {
888 locale_entry = strjoin(locale, " UTF-8");
889 if (!locale_entry)
890 return -ENOMEM;
891 } else
892 return -ENOEXEC; /* We do not process non-UTF-8 locale */
893
894 r = locale_gen_locale_supported(locale_entry);
895 if (r == 0)
896 return -EINVAL;
897 if (r < 0 && r != -EOPNOTSUPP)
898 return r;
899
900 fr = fopen("/etc/locale.gen", "re");
901 if (!fr) {
902 if (errno != ENOENT)
903 return -errno;
904 write_new = true;
905 }
906
907 r = fopen_temporary("/etc/locale.gen", &fw, &temp_path);
908 if (r < 0)
909 return r;
910
911 if (write_new)
912 (void) fchmod(fileno(fw), 0644);
913 else {
914 /* apply mode & xattrs of the original file to new file */
915 r = copy_access(fileno(fr), fileno(fw));
916 if (r < 0)
917 return r;
918 r = copy_xattr(fileno(fr), NULL, fileno(fw), NULL, COPY_ALL_XATTRS);
919 if (r < 0)
920 log_debug_errno(r, "Failed to copy all xattrs from old to new /etc/locale.gen file, ignoring: %m");
921 }
922
923 if (!write_new) {
924 /* The config file ends with a line break, which we do not want to include before potentially appending a new locale
925 * instead of uncommenting an existing line. By prepending linebreaks, we can avoid buffering this file but can still write
926 * a nice config file without empty lines */
927 first_line = true;
928 for (;;) {
929 _cleanup_free_ char *line = NULL;
930 char *line_locale;
931
932 r = read_line(fr, LONG_LINE_MAX, &line);
933 if (r < 0)
934 return r;
935 if (r == 0)
936 break;
937
938 if (locale_enabled) {
939 /* Just complete writing the file if the new locale was already enabled */
940 if (!first_line)
941 fputc('\n', fw);
942 fputs(line, fw);
943 first_line = false;
944 continue;
945 }
946
947 line = strstrip(line);
948 if (isempty(line)) {
949 fputc('\n', fw);
950 first_line = false;
951 continue;
952 }
953
954 line_locale = line;
955 if (line_locale[0] == '#')
956 line_locale = strstrip(line_locale + 1);
957 else if (strcaseeq_ptr(line_locale, locale_entry))
958 return 0; /* the file already had our locale activated, so skip updating it */
959
960 if (strcaseeq_ptr(line_locale, locale_entry)) {
961 /* Uncomment existing line for new locale */
962 if (!first_line)
963 fputc('\n', fw);
964 fputs(locale_entry, fw);
965 locale_enabled = true;
966 first_line = false;
967 continue;
968 }
969
970 /* The line was not for the locale we want to enable, just copy it */
971 if (!first_line)
972 fputc('\n', fw);
973 fputs(line, fw);
974 first_line = false;
975 }
976 }
977
978 /* Add locale to enable to the end of the file if it was not found as commented line */
979 if (!locale_enabled) {
980 if (!write_new)
981 fputc('\n', fw);
982 fputs(locale_entry, fw);
983 }
984 fputc('\n', fw);
985
986 r = fflush_sync_and_check(fw);
987 if (r < 0)
988 return r;
989
990 if (rename(temp_path, "/etc/locale.gen") < 0)
991 return -errno;
992 temp_path = mfree(temp_path);
993
994 return 0;
995 #else
996 return -EOPNOTSUPP;
997 #endif
998 }
999
1000 int locale_gen_run(void) {
1001 #if HAVE_LOCALEGEN
1002 pid_t pid;
1003 int r;
1004
1005 r = safe_fork("(sd-localegen)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, &pid);
1006 if (r < 0)
1007 return r;
1008 if (r == 0) {
1009 execl(LOCALEGEN_PATH, LOCALEGEN_PATH, NULL);
1010 _exit(EXIT_FAILURE);
1011 }
1012
1013 return 0;
1014 #else
1015 return -EOPNOTSUPP;
1016 #endif
1017 }