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