]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
localed: match converted keymaps before legacy
[thirdparty/systemd.git] / src / locale / localectl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7 Copyright 2013 Kay Sievers
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <locale.h>
24 #include <stdlib.h>
25 #include <stdbool.h>
26 #include <unistd.h>
27 #include <getopt.h>
28 #include <string.h>
29 #include <ftw.h>
30 #include <sys/mman.h>
31 #include <fcntl.h>
32
33 #include "sd-bus.h"
34 #include "bus-util.h"
35 #include "bus-error.h"
36 #include "bus-message.h"
37 #include "util.h"
38 #include "spawn-polkit-agent.h"
39 #include "build.h"
40 #include "strv.h"
41 #include "pager.h"
42 #include "set.h"
43 #include "path-util.h"
44 #include "utf8.h"
45 #include "def.h"
46
47 static bool arg_no_pager = false;
48 static bool arg_ask_password = true;
49 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
50 static char *arg_host = NULL;
51 static bool arg_convert = true;
52
53 static void pager_open_if_enabled(void) {
54
55 if (arg_no_pager)
56 return;
57
58 pager_open(false);
59 }
60
61 static void polkit_agent_open_if_enabled(void) {
62
63 /* Open the polkit agent as a child process if necessary */
64 if (!arg_ask_password)
65 return;
66
67 if (arg_transport != BUS_TRANSPORT_LOCAL)
68 return;
69
70 polkit_agent_open();
71 }
72
73 typedef struct StatusInfo {
74 char **locale;
75 const char *vconsole_keymap;
76 const char *vconsole_keymap_toggle;
77 const char *x11_layout;
78 const char *x11_model;
79 const char *x11_variant;
80 const char *x11_options;
81 } StatusInfo;
82
83 static void print_status_info(StatusInfo *i) {
84 assert(i);
85
86 if (strv_isempty(i->locale))
87 puts(" System Locale: n/a\n");
88 else {
89 char **j;
90
91 printf(" System Locale: %s\n", i->locale[0]);
92 STRV_FOREACH(j, i->locale + 1)
93 printf(" %s\n", *j);
94 }
95
96 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
97 if (!isempty(i->vconsole_keymap_toggle))
98 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
99
100 printf(" X11 Layout: %s\n", strna(i->x11_layout));
101 if (!isempty(i->x11_model))
102 printf(" X11 Model: %s\n", i->x11_model);
103 if (!isempty(i->x11_variant))
104 printf(" X11 Variant: %s\n", i->x11_variant);
105 if (!isempty(i->x11_options))
106 printf(" X11 Options: %s\n", i->x11_options);
107 }
108
109 static int show_status(sd_bus *bus, char **args, unsigned n) {
110 StatusInfo info = {};
111 static const struct bus_properties_map map[] = {
112 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
113 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
114 { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
115 { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) },
116 { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) },
117 { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) },
118 { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) },
119 { "Locale", "as", NULL, offsetof(StatusInfo, locale) },
120 {}
121 };
122 int r;
123
124 assert(bus);
125
126 r = bus_map_all_properties(bus,
127 "org.freedesktop.locale1",
128 "/org/freedesktop/locale1",
129 map,
130 &info);
131 if (r < 0)
132 goto fail;
133
134 print_status_info(&info);
135
136 fail:
137 strv_free(info.locale);
138 return r;
139 }
140
141 static int set_locale(sd_bus *bus, char **args, unsigned n) {
142 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
143 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
144 int r;
145
146 assert(bus);
147 assert(args);
148
149 polkit_agent_open_if_enabled();
150
151 r = sd_bus_message_new_method_call(bus,
152 "org.freedesktop.locale1",
153 "/org/freedesktop/locale1",
154 "org.freedesktop.locale1",
155 "SetLocale", &m);
156 if (r < 0)
157 return bus_log_create_error(r);
158
159 r = sd_bus_message_append_strv(m, args + 1);
160 if (r < 0)
161 return bus_log_create_error(r);
162
163 r = sd_bus_message_append(m, "b", arg_ask_password);
164 if (r < 0)
165 return bus_log_create_error(r);
166
167 r = sd_bus_call(bus, m, 0, &error, NULL);
168 if (r < 0) {
169 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
170 return r;
171 }
172
173 return 0;
174 }
175
176 static int add_locales_from_archive(Set *locales) {
177 /* Stolen from glibc... */
178
179 struct locarhead {
180 uint32_t magic;
181 /* Serial number. */
182 uint32_t serial;
183 /* Name hash table. */
184 uint32_t namehash_offset;
185 uint32_t namehash_used;
186 uint32_t namehash_size;
187 /* String table. */
188 uint32_t string_offset;
189 uint32_t string_used;
190 uint32_t string_size;
191 /* Table with locale records. */
192 uint32_t locrectab_offset;
193 uint32_t locrectab_used;
194 uint32_t locrectab_size;
195 /* MD5 sum hash table. */
196 uint32_t sumhash_offset;
197 uint32_t sumhash_used;
198 uint32_t sumhash_size;
199 };
200
201 struct namehashent {
202 /* Hash value of the name. */
203 uint32_t hashval;
204 /* Offset of the name in the string table. */
205 uint32_t name_offset;
206 /* Offset of the locale record. */
207 uint32_t locrec_offset;
208 };
209
210 const struct locarhead *h;
211 const struct namehashent *e;
212 const void *p = MAP_FAILED;
213 _cleanup_close_ int fd = -1;
214 size_t sz = 0;
215 struct stat st;
216 unsigned i;
217 int r;
218
219 fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
220 if (fd < 0) {
221 if (errno != ENOENT)
222 log_error("Failed to open locale archive: %m");
223 r = -errno;
224 goto finish;
225 }
226
227 if (fstat(fd, &st) < 0) {
228 log_error("fstat() failed: %m");
229 r = -errno;
230 goto finish;
231 }
232
233 if (!S_ISREG(st.st_mode)) {
234 log_error("Archive file is not regular");
235 r = -EBADMSG;
236 goto finish;
237 }
238
239 if (st.st_size < (off_t) sizeof(struct locarhead)) {
240 log_error("Archive has invalid size");
241 r = -EBADMSG;
242 goto finish;
243 }
244
245 p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
246 if (p == MAP_FAILED) {
247 log_error("Failed to map archive: %m");
248 r = -errno;
249 goto finish;
250 }
251
252 h = (const struct locarhead *) p;
253 if (h->magic != 0xde020109 ||
254 h->namehash_offset + h->namehash_size > st.st_size ||
255 h->string_offset + h->string_size > st.st_size ||
256 h->locrectab_offset + h->locrectab_size > st.st_size ||
257 h->sumhash_offset + h->sumhash_size > st.st_size) {
258 log_error("Invalid archive file.");
259 r = -EBADMSG;
260 goto finish;
261 }
262
263 e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
264 for (i = 0; i < h->namehash_size; i++) {
265 char *z;
266
267 if (e[i].locrec_offset == 0)
268 continue;
269
270 if (!utf8_is_valid((char*) p + e[i].name_offset))
271 continue;
272
273 z = strdup((char*) p + e[i].name_offset);
274 if (!z) {
275 r = log_oom();
276 goto finish;
277 }
278
279 r = set_consume(locales, z);
280 if (r < 0) {
281 log_error("Failed to add locale: %s", strerror(-r));
282 goto finish;
283 }
284 }
285
286 r = 0;
287
288 finish:
289 if (p != MAP_FAILED)
290 munmap((void*) p, sz);
291
292 return r;
293 }
294
295 static int add_locales_from_libdir (Set *locales) {
296 _cleanup_closedir_ DIR *dir;
297 struct dirent *entry;
298 int r;
299
300 dir = opendir("/usr/lib/locale");
301 if (!dir) {
302 log_error("Failed to open locale directory: %m");
303 return -errno;
304 }
305
306 errno = 0;
307 while ((entry = readdir(dir))) {
308 char *z;
309
310 if (entry->d_type != DT_DIR)
311 continue;
312
313 if (ignore_file(entry->d_name))
314 continue;
315
316 z = strdup(entry->d_name);
317 if (!z)
318 return log_oom();
319
320 r = set_consume(locales, z);
321 if (r < 0 && r != -EEXIST) {
322 log_error("Failed to add locale: %s", strerror(-r));
323 return r;
324 }
325
326 errno = 0;
327 }
328
329 if (errno > 0) {
330 log_error("Failed to read locale directory: %m");
331 return -errno;
332 }
333
334 return 0;
335 }
336
337 static int list_locales(sd_bus *bus, char **args, unsigned n) {
338 _cleanup_set_free_ Set *locales;
339 _cleanup_strv_free_ char **l = NULL;
340 int r;
341
342 locales = set_new(string_hash_func, string_compare_func);
343 if (!locales)
344 return log_oom();
345
346 r = add_locales_from_archive(locales);
347 if (r < 0 && r != -ENOENT)
348 return r;
349
350 r = add_locales_from_libdir(locales);
351 if (r < 0)
352 return r;
353
354 l = set_get_strv(locales);
355 if (!l)
356 return log_oom();
357
358 strv_sort(l);
359
360 pager_open_if_enabled();
361
362 strv_print(l);
363
364 return 0;
365 }
366
367 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
368 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
369 const char *map, *toggle_map;
370 int r;
371
372 assert(bus);
373 assert(args);
374
375 if (n > 3) {
376 log_error("Too many arguments.");
377 return -EINVAL;
378 }
379
380 polkit_agent_open_if_enabled();
381
382 map = args[1];
383 toggle_map = n > 2 ? args[2] : "";
384
385 r = sd_bus_call_method(
386 bus,
387 "org.freedesktop.locale1",
388 "/org/freedesktop/locale1",
389 "org.freedesktop.locale1",
390 "SetVConsoleKeyboard",
391 &error,
392 NULL,
393 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
394 if (r < 0)
395 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
396
397 return r;
398 }
399
400 static Set *keymaps = NULL;
401
402 static int nftw_cb(
403 const char *fpath,
404 const struct stat *sb,
405 int tflag,
406 struct FTW *ftwbuf) {
407
408 char *p, *e;
409 int r;
410
411 if (tflag != FTW_F)
412 return 0;
413
414 if (!endswith(fpath, ".map") &&
415 !endswith(fpath, ".map.gz"))
416 return 0;
417
418 p = strdup(path_get_file_name(fpath));
419 if (!p)
420 return log_oom();
421
422 e = endswith(p, ".map");
423 if (e)
424 *e = 0;
425
426 e = endswith(p, ".map.gz");
427 if (e)
428 *e = 0;
429
430 r = set_consume(keymaps, p);
431 if (r < 0 && r != -EEXIST) {
432 log_error("Can't add keymap: %s", strerror(-r));
433 return r;
434 }
435
436 return 0;
437 }
438
439 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
440 _cleanup_strv_free_ char **l = NULL;
441 const char *dir;
442
443 keymaps = set_new(string_hash_func, string_compare_func);
444 if (!keymaps)
445 return log_oom();
446
447 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
448 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
449
450 l = set_get_strv(keymaps);
451 if (!l) {
452 set_free_free(keymaps);
453 return log_oom();
454 }
455
456 set_free(keymaps);
457
458 if (strv_isempty(l)) {
459 log_error("Couldn't find any console keymaps.");
460 return -ENOENT;
461 }
462
463 strv_sort(l);
464
465 pager_open_if_enabled();
466
467 strv_print(l);
468
469 return 0;
470 }
471
472 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
473 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
474 const char *layout, *model, *variant, *options;
475 int r;
476
477 assert(bus);
478 assert(args);
479
480 if (n > 5) {
481 log_error("Too many arguments.");
482 return -EINVAL;
483 }
484
485 polkit_agent_open_if_enabled();
486
487 layout = args[1];
488 model = n > 2 ? args[2] : "";
489 variant = n > 3 ? args[3] : "";
490 options = n > 4 ? args[4] : "";
491
492 r = sd_bus_call_method(
493 bus,
494 "org.freedesktop.locale1",
495 "/org/freedesktop/locale1",
496 "org.freedesktop.locale1",
497 "SetX11Keyboard",
498 &error,
499 NULL,
500 "ssssbb", layout, model, variant, options,
501 arg_convert, arg_ask_password);
502 if (r < 0)
503 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
504
505 return r;
506 }
507
508 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
509 _cleanup_fclose_ FILE *f = NULL;
510 _cleanup_strv_free_ char **list = NULL;
511 char line[LINE_MAX];
512 enum {
513 NONE,
514 MODELS,
515 LAYOUTS,
516 VARIANTS,
517 OPTIONS
518 } state = NONE, look_for;
519 int r;
520
521 if (n > 2) {
522 log_error("Too many arguments.");
523 return -EINVAL;
524 }
525
526 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
527 if (!f) {
528 log_error("Failed to open keyboard mapping list. %m");
529 return -errno;
530 }
531
532 if (streq(args[0], "list-x11-keymap-models"))
533 look_for = MODELS;
534 else if (streq(args[0], "list-x11-keymap-layouts"))
535 look_for = LAYOUTS;
536 else if (streq(args[0], "list-x11-keymap-variants"))
537 look_for = VARIANTS;
538 else if (streq(args[0], "list-x11-keymap-options"))
539 look_for = OPTIONS;
540 else
541 assert_not_reached("Wrong parameter");
542
543 FOREACH_LINE(line, f, break) {
544 char *l, *w;
545
546 l = strstrip(line);
547
548 if (isempty(l))
549 continue;
550
551 if (l[0] == '!') {
552 if (startswith(l, "! model"))
553 state = MODELS;
554 else if (startswith(l, "! layout"))
555 state = LAYOUTS;
556 else if (startswith(l, "! variant"))
557 state = VARIANTS;
558 else if (startswith(l, "! option"))
559 state = OPTIONS;
560 else
561 state = NONE;
562
563 continue;
564 }
565
566 if (state != look_for)
567 continue;
568
569 w = l + strcspn(l, WHITESPACE);
570
571 if (n > 1) {
572 char *e;
573
574 if (*w == 0)
575 continue;
576
577 *w = 0;
578 w++;
579 w += strspn(w, WHITESPACE);
580
581 e = strchr(w, ':');
582 if (!e)
583 continue;
584
585 *e = 0;
586
587 if (!streq(w, args[1]))
588 continue;
589 } else
590 *w = 0;
591
592 r = strv_extend(&list, l);
593 if (r < 0)
594 return log_oom();
595 }
596
597 if (strv_isempty(list)) {
598 log_error("Couldn't find any entries.");
599 return -ENOENT;
600 }
601
602 strv_sort(list);
603 strv_uniq(list);
604
605 pager_open_if_enabled();
606
607 strv_print(list);
608 return 0;
609 }
610
611 static int help(void) {
612
613 printf("%s [OPTIONS...] COMMAND ...\n\n"
614 "Query or change system locale and keyboard settings.\n\n"
615 " -h --help Show this help\n"
616 " --version Show package version\n"
617 " --no-pager Do not pipe output into a pager\n"
618 " --no-ask-password Do not prompt for password\n"
619 " -H --host=[USER@]HOST Operate on remote host\n"
620 " -M --machine=CONTAINER Operate on local container\n"
621 " --no-convert Don't convert keyboard mappings\n\n"
622 "Commands:\n"
623 " status Show current locale settings\n"
624 " set-locale LOCALE... Set system locale\n"
625 " list-locales Show known locales\n"
626 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
627 " list-keymaps Show known virtual console keyboard mappings\n"
628 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
629 " Set X11 keyboard mapping\n"
630 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
631 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
632 " list-x11-keymap-variants [LAYOUT]\n"
633 " Show known X11 keyboard mapping variants\n"
634 " list-x11-keymap-options Show known X11 keyboard mapping options\n",
635 program_invocation_short_name);
636
637 return 0;
638 }
639
640 static int parse_argv(int argc, char *argv[]) {
641
642 enum {
643 ARG_VERSION = 0x100,
644 ARG_NO_PAGER,
645 ARG_NO_CONVERT,
646 ARG_NO_ASK_PASSWORD
647 };
648
649 static const struct option options[] = {
650 { "help", no_argument, NULL, 'h' },
651 { "version", no_argument, NULL, ARG_VERSION },
652 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
653 { "host", required_argument, NULL, 'H' },
654 { "machine", required_argument, NULL, 'M' },
655 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
656 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
657 {}
658 };
659
660 int c;
661
662 assert(argc >= 0);
663 assert(argv);
664
665 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
666
667 switch (c) {
668
669 case 'h':
670 return help();
671
672 case ARG_VERSION:
673 puts(PACKAGE_STRING);
674 puts(SYSTEMD_FEATURES);
675 return 0;
676
677 case ARG_NO_CONVERT:
678 arg_convert = false;
679 break;
680
681 case ARG_NO_PAGER:
682 arg_no_pager = true;
683 break;
684
685 case ARG_NO_ASK_PASSWORD:
686 arg_ask_password = false;
687 break;
688
689 case 'H':
690 arg_transport = BUS_TRANSPORT_REMOTE;
691 arg_host = optarg;
692 break;
693
694 case 'M':
695 arg_transport = BUS_TRANSPORT_CONTAINER;
696 arg_host = optarg;
697 break;
698
699 case '?':
700 return -EINVAL;
701
702 default:
703 assert_not_reached("Unhandled option");
704 }
705 }
706
707 return 1;
708 }
709
710 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
711
712 static const struct {
713 const char* verb;
714 const enum {
715 MORE,
716 LESS,
717 EQUAL
718 } argc_cmp;
719 const int argc;
720 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
721 } verbs[] = {
722 { "status", LESS, 1, show_status },
723 { "set-locale", MORE, 2, set_locale },
724 { "list-locales", EQUAL, 1, list_locales },
725 { "set-keymap", MORE, 2, set_vconsole_keymap },
726 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
727 { "set-x11-keymap", MORE, 2, set_x11_keymap },
728 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
729 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
730 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
731 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
732 };
733
734 int left;
735 unsigned i;
736
737 assert(argc >= 0);
738 assert(argv);
739
740 left = argc - optind;
741
742 if (left <= 0)
743 /* Special rule: no arguments means "status" */
744 i = 0;
745 else {
746 if (streq(argv[optind], "help")) {
747 help();
748 return 0;
749 }
750
751 for (i = 0; i < ELEMENTSOF(verbs); i++)
752 if (streq(argv[optind], verbs[i].verb))
753 break;
754
755 if (i >= ELEMENTSOF(verbs)) {
756 log_error("Unknown operation %s", argv[optind]);
757 return -EINVAL;
758 }
759 }
760
761 switch (verbs[i].argc_cmp) {
762
763 case EQUAL:
764 if (left != verbs[i].argc) {
765 log_error("Invalid number of arguments.");
766 return -EINVAL;
767 }
768
769 break;
770
771 case MORE:
772 if (left < verbs[i].argc) {
773 log_error("Too few arguments.");
774 return -EINVAL;
775 }
776
777 break;
778
779 case LESS:
780 if (left > verbs[i].argc) {
781 log_error("Too many arguments.");
782 return -EINVAL;
783 }
784
785 break;
786
787 default:
788 assert_not_reached("Unknown comparison operator.");
789 }
790
791 return verbs[i].dispatch(bus, argv + optind, left);
792 }
793
794 int main(int argc, char*argv[]) {
795 _cleanup_bus_unref_ sd_bus *bus = NULL;
796 int r;
797
798 setlocale(LC_ALL, "");
799 log_parse_environment();
800 log_open();
801
802 r = parse_argv(argc, argv);
803 if (r <= 0)
804 goto finish;
805
806 r = bus_open_transport(arg_transport, arg_host, false, &bus);
807 if (r < 0) {
808 log_error("Failed to create bus connection: %s", strerror(-r));
809 goto finish;
810 }
811
812 r = localectl_main(bus, argc, argv);
813
814 finish:
815 pager_close();
816
817 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
818 }