]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
remove unused variables
[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
46 static bool arg_no_pager = false;
47 static bool arg_ask_password = true;
48 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
49 static char *arg_host = NULL;
50 static bool arg_convert = true;
51
52 static void pager_open_if_enabled(void) {
53
54 if (arg_no_pager)
55 return;
56
57 pager_open(false);
58 }
59
60 static void polkit_agent_open_if_enabled(void) {
61
62 /* Open the polkit agent as a child process if necessary */
63 if (!arg_ask_password)
64 return;
65
66 if (arg_transport != BUS_TRANSPORT_LOCAL)
67 return;
68
69 polkit_agent_open();
70 }
71
72 typedef struct StatusInfo {
73 char **locale;
74 const char *vconsole_keymap;
75 const char *vconsole_keymap_toggle;
76 const char *x11_layout;
77 const char *x11_model;
78 const char *x11_variant;
79 const char *x11_options;
80 } StatusInfo;
81
82 static void print_status_info(StatusInfo *i) {
83 assert(i);
84
85 if (strv_isempty(i->locale))
86 puts(" System Locale: n/a\n");
87 else {
88 char **j;
89
90 printf(" System Locale: %s\n", i->locale[0]);
91 STRV_FOREACH(j, i->locale + 1)
92 printf(" %s\n", *j);
93 }
94
95 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
96 if (!isempty(i->vconsole_keymap_toggle))
97 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
98
99 printf(" X11 Layout: %s\n", strna(i->x11_layout));
100 if (!isempty(i->x11_model))
101 printf(" X11 Model: %s\n", i->x11_model);
102 if (!isempty(i->x11_variant))
103 printf(" X11 Variant: %s\n", i->x11_variant);
104 if (!isempty(i->x11_options))
105 printf(" X11 Options: %s\n", i->x11_options);
106 }
107
108 static int show_status(sd_bus *bus, char **args, unsigned n) {
109 StatusInfo info = {};
110 static const struct bus_properties_map map[] = {
111 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
112 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
113 { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
114 { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) },
115 { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) },
116 { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) },
117 { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) },
118 { "Locale", "as", NULL, offsetof(StatusInfo, locale) },
119 {}
120 };
121 int r;
122
123 assert(bus);
124
125 r = bus_map_all_properties(bus,
126 "org.freedesktop.locale1",
127 "/org/freedesktop/locale1",
128 map,
129 &info);
130 if (r < 0)
131 goto fail;
132
133 print_status_info(&info);
134
135 fail:
136 strv_free(info.locale);
137 return r;
138 }
139
140 static int set_locale(sd_bus *bus, char **args, unsigned n) {
141 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
142 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
143 int r;
144
145 assert(bus);
146 assert(args);
147
148 polkit_agent_open_if_enabled();
149
150 r = sd_bus_message_new_method_call(bus,
151 "org.freedesktop.locale1",
152 "/org/freedesktop/locale1",
153 "org.freedesktop.locale1",
154 "SetLocale", &m);
155 if (r < 0)
156 return bus_log_create_error(r);
157
158 r = sd_bus_message_append_strv(m, args + 1);
159 if (r < 0)
160 return bus_log_create_error(r);
161
162 r = sd_bus_message_append(m, "b", arg_ask_password);
163 if (r < 0)
164 return bus_log_create_error(r);
165
166 r = sd_bus_call(bus, m, 0, &error, NULL);
167 if (r < 0) {
168 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
169 return r;
170 }
171
172 return 0;
173 }
174
175 static int add_locales_from_archive(Set *locales) {
176 /* Stolen from glibc... */
177
178 struct locarhead {
179 uint32_t magic;
180 /* Serial number. */
181 uint32_t serial;
182 /* Name hash table. */
183 uint32_t namehash_offset;
184 uint32_t namehash_used;
185 uint32_t namehash_size;
186 /* String table. */
187 uint32_t string_offset;
188 uint32_t string_used;
189 uint32_t string_size;
190 /* Table with locale records. */
191 uint32_t locrectab_offset;
192 uint32_t locrectab_used;
193 uint32_t locrectab_size;
194 /* MD5 sum hash table. */
195 uint32_t sumhash_offset;
196 uint32_t sumhash_used;
197 uint32_t sumhash_size;
198 };
199
200 struct namehashent {
201 /* Hash value of the name. */
202 uint32_t hashval;
203 /* Offset of the name in the string table. */
204 uint32_t name_offset;
205 /* Offset of the locale record. */
206 uint32_t locrec_offset;
207 };
208
209 const struct locarhead *h;
210 const struct namehashent *e;
211 const void *p = MAP_FAILED;
212 _cleanup_close_ int fd = -1;
213 size_t sz = 0;
214 struct stat st;
215 unsigned i;
216 int r;
217
218 fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
219 if (fd < 0) {
220 if (errno != ENOENT)
221 log_error("Failed to open locale archive: %m");
222 r = -errno;
223 goto finish;
224 }
225
226 if (fstat(fd, &st) < 0) {
227 log_error("fstat() failed: %m");
228 r = -errno;
229 goto finish;
230 }
231
232 if (!S_ISREG(st.st_mode)) {
233 log_error("Archive file is not regular");
234 r = -EBADMSG;
235 goto finish;
236 }
237
238 if (st.st_size < (off_t) sizeof(struct locarhead)) {
239 log_error("Archive has invalid size");
240 r = -EBADMSG;
241 goto finish;
242 }
243
244 p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
245 if (p == MAP_FAILED) {
246 log_error("Failed to map archive: %m");
247 r = -errno;
248 goto finish;
249 }
250
251 h = (const struct locarhead *) p;
252 if (h->magic != 0xde020109 ||
253 h->namehash_offset + h->namehash_size > st.st_size ||
254 h->string_offset + h->string_size > st.st_size ||
255 h->locrectab_offset + h->locrectab_size > st.st_size ||
256 h->sumhash_offset + h->sumhash_size > st.st_size) {
257 log_error("Invalid archive file.");
258 r = -EBADMSG;
259 goto finish;
260 }
261
262 e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
263 for (i = 0; i < h->namehash_size; i++) {
264 char *z;
265
266 if (e[i].locrec_offset == 0)
267 continue;
268
269 if (!utf8_is_valid((char*) p + e[i].name_offset))
270 continue;
271
272 z = strdup((char*) p + e[i].name_offset);
273 if (!z) {
274 r = log_oom();
275 goto finish;
276 }
277
278 r = set_consume(locales, z);
279 if (r < 0) {
280 log_error("Failed to add locale: %s", strerror(-r));
281 goto finish;
282 }
283 }
284
285 r = 0;
286
287 finish:
288 if (p != MAP_FAILED)
289 munmap((void*) p, sz);
290
291 return r;
292 }
293
294 static int add_locales_from_libdir (Set *locales) {
295 _cleanup_closedir_ DIR *dir;
296 struct dirent *entry;
297 int r;
298
299 dir = opendir("/usr/lib/locale");
300 if (!dir) {
301 log_error("Failed to open locale directory: %m");
302 return -errno;
303 }
304
305 errno = 0;
306 while ((entry = readdir(dir))) {
307 char *z;
308
309 if (entry->d_type != DT_DIR)
310 continue;
311
312 if (ignore_file(entry->d_name))
313 continue;
314
315 z = strdup(entry->d_name);
316 if (!z)
317 return log_oom();
318
319 r = set_consume(locales, z);
320 if (r < 0 && r != -EEXIST) {
321 log_error("Failed to add locale: %s", strerror(-r));
322 return r;
323 }
324
325 errno = 0;
326 }
327
328 if (errno > 0) {
329 log_error("Failed to read locale directory: %m");
330 return -errno;
331 }
332
333 return 0;
334 }
335
336 static int list_locales(sd_bus *bus, char **args, unsigned n) {
337 _cleanup_set_free_ Set *locales;
338 _cleanup_strv_free_ char **l = NULL;
339 int r;
340
341 locales = set_new(string_hash_func, string_compare_func);
342 if (!locales)
343 return log_oom();
344
345 r = add_locales_from_archive(locales);
346 if (r < 0 && r != -ENOENT)
347 return r;
348
349 r = add_locales_from_libdir(locales);
350 if (r < 0)
351 return r;
352
353 l = set_get_strv(locales);
354 if (!l)
355 return log_oom();
356
357 strv_sort(l);
358
359 pager_open_if_enabled();
360
361 strv_print(l);
362
363 return 0;
364 }
365
366 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
367 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
368 const char *map, *toggle_map;
369 int r;
370
371 assert(bus);
372 assert(args);
373
374 if (n > 3) {
375 log_error("Too many arguments.");
376 return -EINVAL;
377 }
378
379 polkit_agent_open_if_enabled();
380
381 map = args[1];
382 toggle_map = n > 2 ? args[2] : "";
383
384 r = sd_bus_call_method(
385 bus,
386 "org.freedesktop.locale1",
387 "/org/freedesktop/locale1",
388 "org.freedesktop.locale1",
389 "SetVConsoleKeyboard",
390 &error,
391 NULL,
392 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
393 if (r < 0)
394 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
395
396 return r;
397 }
398
399 static Set *keymaps = NULL;
400
401 static int nftw_cb(
402 const char *fpath,
403 const struct stat *sb,
404 int tflag,
405 struct FTW *ftwbuf) {
406
407 char *p, *e;
408 int r;
409
410 if (tflag != FTW_F)
411 return 0;
412
413 if (!endswith(fpath, ".map") &&
414 !endswith(fpath, ".map.gz"))
415 return 0;
416
417 p = strdup(path_get_file_name(fpath));
418 if (!p)
419 return log_oom();
420
421 e = endswith(p, ".map");
422 if (e)
423 *e = 0;
424
425 e = endswith(p, ".map.gz");
426 if (e)
427 *e = 0;
428
429 r = set_consume(keymaps, p);
430 if (r < 0 && r != -EEXIST) {
431 log_error("Can't add keymap: %s", strerror(-r));
432 return r;
433 }
434
435 return 0;
436 }
437
438 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
439 _cleanup_strv_free_ char **l = NULL;
440
441 keymaps = set_new(string_hash_func, string_compare_func);
442 if (!keymaps)
443 return log_oom();
444
445 nftw("/usr/share/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
446 nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
447 nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
448 nftw("/lib/kbd/keymaps/", 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 }