1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "bus-polkit.h"
10 #include "env-file-label.h"
14 #include "fileio-label.h"
18 #include "localed-util.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"
26 #include "tmpfile-util.h"
28 static bool startswith_comma(const char *s
, const char *prefix
) {
32 s
= startswith(s
, prefix
);
36 return IN_SET(*s
, ',', '\0');
39 static const char* systemd_kbd_model_map(void) {
42 s
= getenv("SYSTEMD_KBD_MODEL_MAP");
46 return SYSTEMD_KBD_MODEL_MAP
;
49 static const char* systemd_language_fallback_map(void) {
52 s
= getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
56 return SYSTEMD_LANGUAGE_FALLBACK_MAP
;
59 void x11_context_clear(X11Context
*xc
) {
62 xc
->layout
= mfree(xc
->layout
);
63 xc
->options
= mfree(xc
->options
);
64 xc
->model
= mfree(xc
->model
);
65 xc
->variant
= mfree(xc
->variant
);
68 void x11_context_replace(X11Context
*dest
, X11Context
*src
) {
72 x11_context_clear(dest
);
74 *src
= (X11Context
) {};
77 bool x11_context_isempty(const X11Context
*xc
) {
81 isempty(xc
->layout
) &&
83 isempty(xc
->variant
) &&
87 void x11_context_empty_to_null(X11Context
*xc
) {
90 /* Do not call x11_context_clear() for the passed object. */
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
);
98 bool x11_context_is_safe(const X11Context
*xc
) {
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
));
108 bool x11_context_equal(const X11Context
*a
, const X11Context
*b
) {
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
);
119 int x11_context_copy(X11Context
*dest
, const X11Context
*src
) {
129 modified
= !x11_context_isempty(dest
);
130 x11_context_clear(dest
);
134 r
= free_and_strdup(&dest
->layout
, src
->layout
);
139 r
= free_and_strdup(&dest
->model
, src
->model
);
142 modified
= modified
|| r
> 0;
144 r
= free_and_strdup(&dest
->variant
, src
->variant
);
147 modified
= modified
|| r
> 0;
149 r
= free_and_strdup(&dest
->options
, src
->options
);
152 modified
= modified
|| r
> 0;
157 void vc_context_clear(VCContext
*vc
) {
160 vc
->keymap
= mfree(vc
->keymap
);
161 vc
->toggle
= mfree(vc
->toggle
);
164 void vc_context_replace(VCContext
*dest
, VCContext
*src
) {
168 vc_context_clear(dest
);
170 *src
= (VCContext
) {};
173 bool vc_context_isempty(const VCContext
*vc
) {
177 isempty(vc
->keymap
) &&
181 void vc_context_empty_to_null(VCContext
*vc
) {
184 /* Do not call vc_context_clear() for the passed object. */
186 vc
->keymap
= empty_to_null(vc
->keymap
);
187 vc
->toggle
= empty_to_null(vc
->toggle
);
190 bool vc_context_equal(const VCContext
*a
, const VCContext
*b
) {
195 streq_ptr(a
->keymap
, b
->keymap
) &&
196 streq_ptr(a
->toggle
, b
->toggle
);
199 int vc_context_copy(VCContext
*dest
, const VCContext
*src
) {
209 modified
= !vc_context_isempty(dest
);
210 vc_context_clear(dest
);
214 r
= free_and_strdup(&dest
->keymap
, src
->keymap
);
219 r
= free_and_strdup(&dest
->toggle
, src
->toggle
);
222 modified
= modified
|| r
> 0;
227 void context_clear(Context
*c
) {
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
);
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
);
239 c
->polkit_registry
= bus_verify_polkit_async_registry_free(c
->polkit_registry
);
242 X11Context
*context_get_x11_context(Context
*c
) {
245 if (!x11_context_isempty(&c
->x11_from_vc
))
246 return &c
->x11_from_vc
;
248 if (!x11_context_isempty(&c
->x11_from_xorg
))
249 return &c
->x11_from_xorg
;
251 return &c
->x11_from_vc
;
254 int locale_read_data(Context
*c
, sd_bus_message
*m
) {
257 /* Do not try to re-read the file within single bus operation. */
259 if (m
== c
->locale_cache
)
262 sd_bus_message_unref(c
->locale_cache
);
263 c
->locale_cache
= sd_bus_message_ref(m
);
266 return locale_context_load(&c
->locale_context
, LOCALE_LOAD_LOCALE_CONF
| LOCALE_LOAD_ENVIRONMENT
| LOCALE_LOAD_SIMPLIFY
);
269 int vconsole_read_data(Context
*c
, sd_bus_message
*m
) {
270 _cleanup_close_
int fd
= -EBADF
;
275 /* Do not try to re-read the file within single bus operation. */
277 if (m
== c
->vc_cache
)
280 sd_bus_message_unref(c
->vc_cache
);
281 c
->vc_cache
= sd_bus_message_ref(m
);
284 fd
= RET_NERRNO(open("/etc/vconsole.conf", O_CLOEXEC
| O_PATH
));
286 c
->vc_stat
= (struct stat
) {};
287 vc_context_clear(&c
->vc
);
288 x11_context_clear(&c
->x11_from_vc
);
294 if (fstat(fd
, &st
) < 0)
297 /* If the file is not changed, then we do not need to re-read */
298 if (stat_inode_unmodified(&c
->vc_stat
, &st
))
302 vc_context_clear(&c
->vc
);
303 x11_context_clear(&c
->x11_from_vc
);
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
);
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;
323 /* Do not try to re-read the file within single bus operation. */
325 if (m
== c
->x11_cache
)
328 sd_bus_message_unref(c
->x11_cache
);
329 c
->x11_cache
= sd_bus_message_ref(m
);
332 fd
= RET_NERRNO(open("/etc/X11/xorg.conf.d/00-keyboard.conf", O_CLOEXEC
| O_PATH
));
334 c
->x11_stat
= (struct stat
) {};
335 x11_context_clear(&c
->x11_from_xorg
);
341 if (fstat(fd
, &st
) < 0)
344 /* If the file is not changed, then we do not need to re-read */
345 if (stat_inode_unmodified(&c
->x11_stat
, &st
))
349 x11_context_clear(&c
->x11_from_xorg
);
351 r
= fdopen_independent(fd
, "re", &f
);
356 _cleanup_free_
char *line
= NULL
;
359 r
= read_line(f
, LONG_LINE_MAX
, &line
);
366 if (IN_SET(l
[0], 0, '#'))
369 if (in_section
&& first_word(l
, "Option")) {
370 _cleanup_strv_free_
char **a
= NULL
;
372 r
= strv_split_full(&a
, l
, WHITESPACE
, EXTRACT_UNQUOTE
);
376 if (strv_length(a
) == 3) {
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
;
389 free_and_replace(*p
, a
[2]);
392 } else if (!in_section
&& first_word(l
, "Section")) {
393 _cleanup_strv_free_
char **a
= NULL
;
395 r
= strv_split_full(&a
, l
, WHITESPACE
, EXTRACT_UNQUOTE
);
399 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
402 } else if (in_section
&& first_word(l
, "EndSection"))
409 int vconsole_write_data(Context
*c
) {
410 _cleanup_strv_free_
char **l
= NULL
;
411 const X11Context
*xc
;
416 xc
= context_get_x11_context(c
);
418 r
= load_env_file(NULL
, "/etc/vconsole.conf", &l
);
419 if (r
< 0 && r
!= -ENOENT
)
422 r
= strv_env_assign(&l
, "KEYMAP", empty_to_null(c
->vc
.keymap
));
426 r
= strv_env_assign(&l
, "KEYMAP_TOGGLE", empty_to_null(c
->vc
.toggle
));
430 r
= strv_env_assign(&l
, "XKBLAYOUT", empty_to_null(xc
->layout
));
434 r
= strv_env_assign(&l
, "XKBMODEL", empty_to_null(xc
->model
));
438 r
= strv_env_assign(&l
, "XKBVARIANT", empty_to_null(xc
->variant
));
442 r
= strv_env_assign(&l
, "XKBOPTIONS", empty_to_null(xc
->options
));
446 if (strv_isempty(l
)) {
447 if (unlink("/etc/vconsole.conf") < 0)
448 return errno
== ENOENT
? 0 : -errno
;
450 c
->vc_stat
= (struct stat
) {};
454 r
= write_env_file_label("/etc/vconsole.conf", l
);
458 if (stat("/etc/vconsole.conf", &c
->vc_stat
) < 0)
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
;
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
;
477 c
->x11_stat
= (struct stat
) {};
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
);
486 (void) fchmod(fileno(f
), 0644);
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
);
495 if (!isempty(xc
->layout
))
496 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", xc
->layout
);
498 if (!isempty(xc
->model
))
499 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", xc
->model
);
501 if (!isempty(xc
->variant
))
502 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", xc
->variant
);
504 if (!isempty(xc
->options
))
505 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", xc
->options
);
507 fputs("EndSection\n", f
);
509 r
= fflush_sync_and_check(f
);
513 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
516 if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &c
->x11_stat
) < 0)
522 static int read_next_mapping(
523 const char *filename
,
535 _cleanup_strv_free_
char **b
= NULL
;
536 _cleanup_free_
char *line
= NULL
;
541 r
= read_line(f
, LONG_LINE_MAX
, &line
);
550 if (IN_SET(l
[0], 0, '#'))
553 r
= strv_split_full(&b
, l
, WHITESPACE
, EXTRACT_UNQUOTE
);
557 length
= strv_length(b
);
558 if (length
< min_fields
|| length
> max_fields
) {
559 log_debug("Invalid line %s:%u, ignoring.", strna(filename
), *n
);
572 int vconsole_convert_to_x11(const VCContext
*vc
, X11Context
*ret
) {
573 _cleanup_fclose_
FILE *f
= NULL
;
580 if (isempty(vc
->keymap
)) {
581 *ret
= (X11Context
) {};
585 map
= systemd_kbd_model_map();
586 f
= fopen(map
, "re");
590 for (unsigned n
= 0;;) {
591 _cleanup_strv_free_
char **a
= NULL
;
593 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
597 *ret
= (X11Context
) {};
601 if (!streq(vc
->keymap
, a
[0]))
604 return x11_context_copy(ret
,
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]),
614 int find_converted_keymap(const X11Context
*xc
, char **ret
) {
615 _cleanup_free_
char *n
= NULL
;
618 assert(!isempty(xc
->layout
));
622 n
= strjoin(xc
->layout
, "-", xc
->variant
);
624 n
= strdup(xc
->layout
);
628 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
629 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
632 p
= strjoin(dir
, "xkb/", n
, ".map");
633 pz
= strjoin(dir
, "xkb/", n
, ".map.gz");
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
);
649 int find_legacy_keymap(const X11Context
*xc
, char **ret
) {
651 _cleanup_fclose_
FILE *f
= NULL
;
652 _cleanup_free_
char *new_keymap
= NULL
;
653 unsigned best_matching
= 0;
657 assert(!isempty(xc
->layout
));
659 map
= systemd_kbd_model_map();
660 f
= fopen(map
, "re");
664 for (unsigned n
= 0;;) {
665 _cleanup_strv_free_
char **a
= NULL
;
666 unsigned matching
= 0;
668 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
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 */
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]))
685 _cleanup_free_
char *x
= NULL
;
687 /* If that didn't work, strip off the
688 * other layouts from the entry, too */
689 x
= strdupcspn(a
[1], ",");
692 if (startswith_comma(xc
->layout
, x
))
698 if (isempty(xc
->model
) || streq_ptr(xc
->model
, a
[2])) {
701 if (streq_ptr(xc
->variant
, a
[3])) {
704 if (streq_ptr(xc
->options
, a
[4]))
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
);
714 if (matching
> best_matching
) {
715 best_matching
= matching
;
717 r
= free_and_strdup(&new_keymap
, a
[0]);
724 if (best_matching
< 10 && !isempty(xc
->layout
)) {
725 _cleanup_free_
char *l
= NULL
, *v
= NULL
, *converted
= NULL
;
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.
732 l
= strndup(xc
->layout
, strcspn(xc
->layout
, ","));
736 if (!isempty(xc
->variant
)) {
737 v
= strndup(xc
->variant
, strcspn(xc
->variant
, ","));
742 r
= find_converted_keymap(
751 free_and_replace(new_keymap
, converted
);
754 *ret
= TAKE_PTR(new_keymap
);
758 int find_language_fallback(const char *lang
, char **ret
) {
760 _cleanup_fclose_
FILE *f
= NULL
;
767 map
= systemd_language_fallback_map();
768 f
= fopen(map
, "re");
773 _cleanup_strv_free_
char **a
= NULL
;
775 r
= read_next_mapping(map
, 2, 2, f
, &n
, &a
);
779 if (streq(lang
, a
[0])) {
780 assert(strv_length(a
) == 2);
781 *ret
= TAKE_PTR(a
[1]);
787 int x11_convert_to_vconsole(const X11Context
*xc
, VCContext
*ret
) {
788 _cleanup_free_
char *keymap
= NULL
;
794 if (isempty(xc
->layout
)) {
795 *ret
= (VCContext
) {};
799 r
= find_converted_keymap(xc
, &keymap
);
801 r
= find_legacy_keymap(xc
, &keymap
);
806 .keymap
= TAKE_PTR(keymap
),
811 bool locale_gen_check_available(void) {
813 if (access(LOCALEGEN_PATH
, X_OK
) < 0) {
815 log_warning_errno(errno
, "Unable to determine whether " LOCALEGEN_PATH
" exists and is executable, assuming it is not: %m");
818 if (access("/etc/locale.gen", F_OK
) < 0) {
820 log_warning_errno(errno
, "Unable to determine whether /etc/locale.gen exists, assuming it does not: %m");
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@");
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. */
841 _cleanup_fclose_
FILE *f
= NULL
;
844 assert(locale_entry
);
846 /* Locale templates without country code are never supported */
847 if (!strstr(locale_entry
, "_"))
850 f
= fopen("/usr/share/i18n/SUPPORTED", "re");
853 return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
854 "Unable to check validity of locale entry %s: /usr/share/i18n/SUPPORTED does not exist",
860 _cleanup_free_
char *line
= NULL
;
862 r
= read_line(f
, LONG_LINE_MAX
, &line
);
864 return log_debug_errno(r
, "Failed to read /usr/share/i18n/SUPPORTED: %m");
868 line
= strstrip(line
);
869 if (strcaseeq_ptr(line
, locale_entry
))
875 int locale_gen_enable_locale(const char *locale
) {
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;
887 if (locale_encoding_is_utf8_or_unspecified(locale
)) {
888 locale_entry
= strjoin(locale
, " UTF-8");
892 return -ENOEXEC
; /* We do not process non-UTF-8 locale */
894 r
= locale_gen_locale_supported(locale_entry
);
897 if (r
< 0 && r
!= -EOPNOTSUPP
)
900 fr
= fopen("/etc/locale.gen", "re");
907 r
= fopen_temporary("/etc/locale.gen", &fw
, &temp_path
);
912 (void) fchmod(fileno(fw
), 0644);
914 /* apply mode & xattrs of the original file to new file */
915 r
= copy_access(fileno(fr
), fileno(fw
));
918 r
= copy_xattr(fileno(fr
), NULL
, fileno(fw
), NULL
, COPY_ALL_XATTRS
);
920 log_debug_errno(r
, "Failed to copy all xattrs from old to new /etc/locale.gen file, ignoring: %m");
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 */
929 _cleanup_free_
char *line
= NULL
;
932 r
= read_line(fr
, LONG_LINE_MAX
, &line
);
938 if (locale_enabled
) {
939 /* Just complete writing the file if the new locale was already enabled */
947 line
= strstrip(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 */
960 if (strcaseeq_ptr(line_locale
, locale_entry
)) {
961 /* Uncomment existing line for new locale */
964 fputs(locale_entry
, fw
);
965 locale_enabled
= true;
970 /* The line was not for the locale we want to enable, just copy it */
978 /* Add locale to enable to the end of the file if it was not found as commented line */
979 if (!locale_enabled
) {
982 fputs(locale_entry
, fw
);
986 r
= fflush_sync_and_check(fw
);
990 if (rename(temp_path
, "/etc/locale.gen") < 0)
992 temp_path
= mfree(temp_path
);
1000 int locale_gen_run(void) {
1005 r
= safe_fork("(sd-localegen)", FORK_RESET_SIGNALS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_CLOSE_ALL_FDS
|FORK_LOG
|FORK_WAIT
, &pid
);
1009 execl(LOCALEGEN_PATH
, LOCALEGEN_PATH
, NULL
);
1010 _exit(EXIT_FAILURE
);