2 This file is part of systemd.
4 Copyright 2011 Lennart Poettering
5 Copyright 2013 Kay Sievers
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include "fileio-label.h"
30 #include "keymap-util.h"
31 #include "locale-util.h"
34 #include "string-util.h"
37 static bool startswith_comma(const char *s
, const char *prefix
) {
40 return s
&& (t
= startswith(s
, prefix
)) && (*t
== ',');
43 static const char* strnulldash(const char *s
) {
44 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
47 static void context_free_x11(Context
*c
) {
48 c
->x11_layout
= mfree(c
->x11_layout
);
49 c
->x11_options
= mfree(c
->x11_options
);
50 c
->x11_model
= mfree(c
->x11_model
);
51 c
->x11_variant
= mfree(c
->x11_variant
);
54 static void context_free_vconsole(Context
*c
) {
55 c
->vc_keymap
= mfree(c
->vc_keymap
);
56 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
59 static void context_free_locale(Context
*c
) {
62 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
63 c
->locale
[p
] = mfree(c
->locale
[p
]);
66 void context_free(Context
*c
) {
67 context_free_locale(c
);
69 context_free_vconsole(c
);
72 void locale_simplify(Context
*c
) {
75 for (p
= VARIABLE_LANG
+1; p
< _VARIABLE_LC_MAX
; p
++)
76 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[VARIABLE_LANG
], c
->locale
[p
]))
77 c
->locale
[p
] = mfree(c
->locale
[p
]);
80 static int locale_read_data(Context
*c
) {
83 context_free_locale(c
);
85 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
86 "LANG", &c
->locale
[VARIABLE_LANG
],
87 "LANGUAGE", &c
->locale
[VARIABLE_LANGUAGE
],
88 "LC_CTYPE", &c
->locale
[VARIABLE_LC_CTYPE
],
89 "LC_NUMERIC", &c
->locale
[VARIABLE_LC_NUMERIC
],
90 "LC_TIME", &c
->locale
[VARIABLE_LC_TIME
],
91 "LC_COLLATE", &c
->locale
[VARIABLE_LC_COLLATE
],
92 "LC_MONETARY", &c
->locale
[VARIABLE_LC_MONETARY
],
93 "LC_MESSAGES", &c
->locale
[VARIABLE_LC_MESSAGES
],
94 "LC_PAPER", &c
->locale
[VARIABLE_LC_PAPER
],
95 "LC_NAME", &c
->locale
[VARIABLE_LC_NAME
],
96 "LC_ADDRESS", &c
->locale
[VARIABLE_LC_ADDRESS
],
97 "LC_TELEPHONE", &c
->locale
[VARIABLE_LC_TELEPHONE
],
98 "LC_MEASUREMENT", &c
->locale
[VARIABLE_LC_MEASUREMENT
],
99 "LC_IDENTIFICATION", &c
->locale
[VARIABLE_LC_IDENTIFICATION
],
105 /* Fill in what we got passed from systemd. */
106 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
109 name
= locale_variable_to_string(p
);
112 r
= free_and_strdup(&c
->locale
[p
], empty_to_null(getenv(name
)));
124 static int vconsole_read_data(Context
*c
) {
127 context_free_vconsole(c
);
129 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
130 "KEYMAP", &c
->vc_keymap
,
131 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
134 if (r
< 0 && r
!= -ENOENT
)
140 static int x11_read_data(Context
*c
) {
141 _cleanup_fclose_
FILE *f
;
143 bool in_section
= false;
148 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
150 return errno
== ENOENT
? 0 : -errno
;
152 while (fgets(line
, sizeof(line
), f
)) {
158 if (l
[0] == 0 || l
[0] == '#')
161 if (in_section
&& first_word(l
, "Option")) {
162 _cleanup_strv_free_
char **a
= NULL
;
164 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
168 if (strv_length(a
) == 3) {
171 if (streq(a
[1], "XkbLayout"))
173 else if (streq(a
[1], "XkbModel"))
175 else if (streq(a
[1], "XkbVariant"))
177 else if (streq(a
[1], "XkbOptions"))
187 } else if (!in_section
&& first_word(l
, "Section")) {
188 _cleanup_strv_free_
char **a
= NULL
;
190 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
194 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
197 } else if (in_section
&& first_word(l
, "EndSection"))
204 int context_read_data(Context
*c
) {
207 r
= locale_read_data(c
);
208 q
= vconsole_read_data(c
);
209 p
= x11_read_data(c
);
211 return r
< 0 ? r
: q
< 0 ? q
: p
;
214 int locale_write_data(Context
*c
, char ***settings
) {
216 _cleanup_strv_free_
char **l
= NULL
;
218 /* Set values will be returned as strv in *settings on success. */
220 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
221 if (r
< 0 && r
!= -ENOENT
)
224 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
225 _cleanup_free_
char *t
= NULL
;
229 name
= locale_variable_to_string(p
);
232 if (isempty(c
->locale
[p
])) {
233 l
= strv_env_unset(l
, name
);
237 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
240 u
= strv_env_set(l
, t
);
248 if (strv_isempty(l
)) {
249 if (unlink("/etc/locale.conf") < 0)
250 return errno
== ENOENT
? 0 : -errno
;
255 r
= write_env_file_label("/etc/locale.conf", l
);
264 int vconsole_write_data(Context
*c
) {
266 _cleanup_strv_free_
char **l
= NULL
;
268 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
269 if (r
< 0 && r
!= -ENOENT
)
272 if (isempty(c
->vc_keymap
))
273 l
= strv_env_unset(l
, "KEYMAP");
275 _cleanup_free_
char *s
= NULL
;
278 s
= strappend("KEYMAP=", c
->vc_keymap
);
282 u
= strv_env_set(l
, s
);
290 if (isempty(c
->vc_keymap_toggle
))
291 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
293 _cleanup_free_
char *s
= NULL
;
296 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
300 u
= strv_env_set(l
, s
);
308 if (strv_isempty(l
)) {
309 if (unlink("/etc/vconsole.conf") < 0)
310 return errno
== ENOENT
? 0 : -errno
;
315 return write_env_file_label("/etc/vconsole.conf", l
);
318 int x11_write_data(Context
*c
) {
319 _cleanup_fclose_
FILE *f
= NULL
;
320 _cleanup_free_
char *temp_path
= NULL
;
323 if (isempty(c
->x11_layout
) &&
324 isempty(c
->x11_model
) &&
325 isempty(c
->x11_variant
) &&
326 isempty(c
->x11_options
)) {
328 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
329 return errno
== ENOENT
? 0 : -errno
;
334 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
336 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
340 fchmod(fileno(f
), 0644);
342 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
343 "# manually too freely.\n"
344 "Section \"InputClass\"\n"
345 " Identifier \"system-keyboard\"\n"
346 " MatchIsKeyboard \"on\"\n", f
);
348 if (!isempty(c
->x11_layout
))
349 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
351 if (!isempty(c
->x11_model
))
352 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
354 if (!isempty(c
->x11_variant
))
355 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
357 if (!isempty(c
->x11_options
))
358 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
360 fputs("EndSection\n", f
);
362 r
= fflush_and_check(f
);
366 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
374 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
377 (void) unlink(temp_path
);
382 static int read_next_mapping(const char* filename
,
383 unsigned min_fields
, unsigned max_fields
,
384 FILE *f
, unsigned *n
, char ***a
) {
396 if (!fgets(line
, sizeof(line
), f
)) {
399 return errno
> 0 ? -errno
: -EIO
;
407 if (l
[0] == 0 || l
[0] == '#')
410 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
414 length
= strv_length(b
);
415 if (length
< min_fields
|| length
> max_fields
) {
416 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
427 int vconsole_convert_to_x11(Context
*c
) {
428 bool modified
= false;
430 if (isempty(c
->vc_keymap
)) {
433 !isempty(c
->x11_layout
) ||
434 !isempty(c
->x11_model
) ||
435 !isempty(c
->x11_variant
) ||
436 !isempty(c
->x11_options
);
440 _cleanup_fclose_
FILE *f
= NULL
;
443 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
448 _cleanup_strv_free_
char **a
= NULL
;
451 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
457 if (!streq(c
->vc_keymap
, a
[0]))
460 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
461 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
462 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
463 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
465 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
466 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
467 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
468 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
479 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
480 strempty(c
->x11_layout
),
481 strempty(c
->x11_model
),
482 strempty(c
->x11_variant
),
483 strempty(c
->x11_options
));
486 log_debug("X11 keyboard layout was not modified.");
491 int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
493 _cleanup_free_
char *n
;
496 n
= strjoin(x11_layout
, "-", x11_variant
, NULL
);
498 n
= strdup(x11_layout
);
502 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
503 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
506 p
= strjoin(dir
, "xkb/", n
, ".map", NULL
);
507 pz
= strjoin(dir
, "xkb/", n
, ".map.gz", NULL
);
511 uncompressed
= access(p
, F_OK
) == 0;
512 if (uncompressed
|| access(pz
, F_OK
) == 0) {
513 log_debug("Found converted keymap %s at %s",
514 n
, uncompressed
? p
: pz
);
525 static int find_legacy_keymap(Context
*c
, char **new_keymap
) {
526 _cleanup_fclose_
FILE *f
;
528 unsigned best_matching
= 0;
531 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
536 _cleanup_strv_free_
char **a
= NULL
;
537 unsigned matching
= 0;
539 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
545 /* Determine how well matching this entry is */
546 if (streq_ptr(c
->x11_layout
, a
[1]))
547 /* If we got an exact match, this is best */
550 /* We have multiple X layouts, look for an
551 * entry that matches our key with everything
552 * but the first layout stripped off. */
553 if (startswith_comma(c
->x11_layout
, a
[1]))
558 /* If that didn't work, strip off the
559 * other layouts from the entry, too */
560 x
= strndupa(a
[1], strcspn(a
[1], ","));
561 if (startswith_comma(c
->x11_layout
, x
))
567 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
570 if (streq_ptr(c
->x11_variant
, a
[3])) {
573 if (streq_ptr(c
->x11_options
, a
[4]))
579 /* The best matching entry so far, then let's save that */
580 if (matching
>= MAX(best_matching
, 1u)) {
581 log_debug("Found legacy keymap %s with score %u",
584 if (matching
> best_matching
) {
585 best_matching
= matching
;
587 r
= free_and_strdup(new_keymap
, a
[0]);
594 if (best_matching
< 10 && c
->x11_layout
) {
595 /* The best match is only the first part of the X11
596 * keymap. Check if we have a converted map which
597 * matches just the first layout.
599 char *l
, *v
= NULL
, *converted
;
601 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
603 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
604 r
= find_converted_keymap(l
, v
, &converted
);
609 *new_keymap
= converted
;
616 int find_language_fallback(const char *lang
, char **language
) {
617 _cleanup_fclose_
FILE *f
= NULL
;
622 f
= fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP
, "re");
627 _cleanup_strv_free_
char **a
= NULL
;
630 r
= read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP
, 2, 2, f
, &n
, &a
);
634 if (streq(lang
, a
[0])) {
635 assert(strv_length(a
) == 2);
642 assert_not_reached("should not be here");
645 int x11_convert_to_vconsole(Context
*c
) {
646 bool modified
= false;
648 if (isempty(c
->x11_layout
)) {
651 !isempty(c
->vc_keymap
) ||
652 !isempty(c
->vc_keymap_toggle
);
656 char *new_keymap
= NULL
;
659 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
663 r
= find_legacy_keymap(c
, &new_keymap
);
668 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
670 c
->vc_keymap
= new_keymap
;
671 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
678 log_info("Changing virtual console keymap to '%s' toggle '%s'",
679 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
681 log_debug("Virtual console keymap was not modified.");