1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
7 Copyright 2013 Kay Sievers
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.
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.
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/>.
35 #include "bus-error.h"
36 #include "bus-message.h"
38 #include "spawn-polkit-agent.h"
43 #include "path-util.h"
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;
53 static void pager_open_if_enabled(void) {
61 static void polkit_agent_open_if_enabled(void) {
63 /* Open the polkit agent as a child process if necessary */
64 if (!arg_ask_password)
67 if (arg_transport != BUS_TRANSPORT_LOCAL)
73 typedef struct StatusInfo {
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;
83 static void print_status_info(StatusInfo *i) {
86 if (strv_isempty(i->locale))
87 puts(" System Locale: n/a\n");
91 printf(" System Locale: %s\n", i->locale[0]);
92 STRV_FOREACH(j, i->locale + 1)
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);
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);
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) },
126 r = bus_map_all_properties(bus,
127 "org.freedesktop.locale1",
128 "/org/freedesktop/locale1",
132 log_error("Could not get properties: %s", strerror(-r));
136 print_status_info(&info);
139 strv_free(info.locale);
143 static int set_locale(sd_bus *bus, char **args, unsigned n) {
144 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
145 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
151 polkit_agent_open_if_enabled();
153 r = sd_bus_message_new_method_call(
156 "org.freedesktop.locale1",
157 "/org/freedesktop/locale1",
158 "org.freedesktop.locale1",
161 return bus_log_create_error(r);
163 r = sd_bus_message_append_strv(m, args + 1);
165 return bus_log_create_error(r);
167 r = sd_bus_message_append(m, "b", arg_ask_password);
169 return bus_log_create_error(r);
171 r = sd_bus_call(bus, m, 0, &error, NULL);
173 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
180 static int add_locales_from_archive(Set *locales) {
181 /* Stolen from glibc... */
187 /* Name hash table. */
188 uint32_t namehash_offset;
189 uint32_t namehash_used;
190 uint32_t namehash_size;
192 uint32_t string_offset;
193 uint32_t string_used;
194 uint32_t string_size;
195 /* Table with locale records. */
196 uint32_t locrectab_offset;
197 uint32_t locrectab_used;
198 uint32_t locrectab_size;
199 /* MD5 sum hash table. */
200 uint32_t sumhash_offset;
201 uint32_t sumhash_used;
202 uint32_t sumhash_size;
206 /* Hash value of the name. */
208 /* Offset of the name in the string table. */
209 uint32_t name_offset;
210 /* Offset of the locale record. */
211 uint32_t locrec_offset;
214 const struct locarhead *h;
215 const struct namehashent *e;
216 const void *p = MAP_FAILED;
217 _cleanup_close_ int fd = -1;
223 fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
226 log_error("Failed to open locale archive: %m");
231 if (fstat(fd, &st) < 0) {
232 log_error("fstat() failed: %m");
237 if (!S_ISREG(st.st_mode)) {
238 log_error("Archive file is not regular");
243 if (st.st_size < (off_t) sizeof(struct locarhead)) {
244 log_error("Archive has invalid size");
249 p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
250 if (p == MAP_FAILED) {
251 log_error("Failed to map archive: %m");
256 h = (const struct locarhead *) p;
257 if (h->magic != 0xde020109 ||
258 h->namehash_offset + h->namehash_size > st.st_size ||
259 h->string_offset + h->string_size > st.st_size ||
260 h->locrectab_offset + h->locrectab_size > st.st_size ||
261 h->sumhash_offset + h->sumhash_size > st.st_size) {
262 log_error("Invalid archive file.");
267 e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
268 for (i = 0; i < h->namehash_size; i++) {
271 if (e[i].locrec_offset == 0)
274 if (!utf8_is_valid((char*) p + e[i].name_offset))
277 z = strdup((char*) p + e[i].name_offset);
283 r = set_consume(locales, z);
285 log_error("Failed to add locale: %s", strerror(-r));
294 munmap((void*) p, sz);
299 static int add_locales_from_libdir (Set *locales) {
300 _cleanup_closedir_ DIR *dir;
301 struct dirent *entry;
304 dir = opendir("/usr/lib/locale");
306 log_error("Failed to open locale directory: %m");
311 while ((entry = readdir(dir))) {
314 if (entry->d_type != DT_DIR)
317 if (ignore_file(entry->d_name))
320 z = strdup(entry->d_name);
324 r = set_consume(locales, z);
325 if (r < 0 && r != -EEXIST) {
326 log_error("Failed to add locale: %s", strerror(-r));
334 log_error("Failed to read locale directory: %m");
341 static int list_locales(sd_bus *bus, char **args, unsigned n) {
342 _cleanup_set_free_ Set *locales;
343 _cleanup_strv_free_ char **l = NULL;
346 locales = set_new(string_hash_func, string_compare_func);
350 r = add_locales_from_archive(locales);
351 if (r < 0 && r != -ENOENT)
354 r = add_locales_from_libdir(locales);
358 l = set_get_strv(locales);
364 pager_open_if_enabled();
371 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
372 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
373 const char *map, *toggle_map;
380 log_error("Too many arguments.");
384 polkit_agent_open_if_enabled();
387 toggle_map = n > 2 ? args[2] : "";
389 r = sd_bus_call_method(
391 "org.freedesktop.locale1",
392 "/org/freedesktop/locale1",
393 "org.freedesktop.locale1",
394 "SetVConsoleKeyboard",
397 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
399 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
404 static Set *keymaps = NULL;
408 const struct stat *sb,
410 struct FTW *ftwbuf) {
418 if (!endswith(fpath, ".map") &&
419 !endswith(fpath, ".map.gz"))
422 p = strdup(basename(fpath));
426 e = endswith(p, ".map");
430 e = endswith(p, ".map.gz");
434 r = set_consume(keymaps, p);
435 if (r < 0 && r != -EEXIST) {
436 log_error("Can't add keymap: %s", strerror(-r));
443 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
444 _cleanup_strv_free_ char **l = NULL;
447 keymaps = set_new(string_hash_func, string_compare_func);
451 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
452 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
454 l = set_get_strv(keymaps);
456 set_free_free(keymaps);
462 if (strv_isempty(l)) {
463 log_error("Couldn't find any console keymaps.");
469 pager_open_if_enabled();
476 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
477 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
478 const char *layout, *model, *variant, *options;
485 log_error("Too many arguments.");
489 polkit_agent_open_if_enabled();
492 model = n > 2 ? args[2] : "";
493 variant = n > 3 ? args[3] : "";
494 options = n > 4 ? args[4] : "";
496 r = sd_bus_call_method(
498 "org.freedesktop.locale1",
499 "/org/freedesktop/locale1",
500 "org.freedesktop.locale1",
504 "ssssbb", layout, model, variant, options,
505 arg_convert, arg_ask_password);
507 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
512 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
513 _cleanup_fclose_ FILE *f = NULL;
514 _cleanup_strv_free_ char **list = NULL;
522 } state = NONE, look_for;
526 log_error("Too many arguments.");
530 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
532 log_error("Failed to open keyboard mapping list. %m");
536 if (streq(args[0], "list-x11-keymap-models"))
538 else if (streq(args[0], "list-x11-keymap-layouts"))
540 else if (streq(args[0], "list-x11-keymap-variants"))
542 else if (streq(args[0], "list-x11-keymap-options"))
545 assert_not_reached("Wrong parameter");
547 FOREACH_LINE(line, f, break) {
556 if (startswith(l, "! model"))
558 else if (startswith(l, "! layout"))
560 else if (startswith(l, "! variant"))
562 else if (startswith(l, "! option"))
570 if (state != look_for)
573 w = l + strcspn(l, WHITESPACE);
583 w += strspn(w, WHITESPACE);
591 if (!streq(w, args[1]))
596 r = strv_extend(&list, l);
601 if (strv_isempty(list)) {
602 log_error("Couldn't find any entries.");
609 pager_open_if_enabled();
615 static int help(void) {
617 printf("%s [OPTIONS...] COMMAND ...\n\n"
618 "Query or change system locale and keyboard settings.\n\n"
619 " -h --help Show this help\n"
620 " --version Show package version\n"
621 " --no-pager Do not pipe output into a pager\n"
622 " --no-ask-password Do not prompt for password\n"
623 " -H --host=[USER@]HOST Operate on remote host\n"
624 " -M --machine=CONTAINER Operate on local container\n"
625 " --no-convert Don't convert keyboard mappings\n\n"
627 " status Show current locale settings\n"
628 " set-locale LOCALE... Set system locale\n"
629 " list-locales Show known locales\n"
630 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
631 " list-keymaps Show known virtual console keyboard mappings\n"
632 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
633 " Set X11 keyboard mapping\n"
634 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
635 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
636 " list-x11-keymap-variants [LAYOUT]\n"
637 " Show known X11 keyboard mapping variants\n"
638 " list-x11-keymap-options Show known X11 keyboard mapping options\n",
639 program_invocation_short_name);
644 static int parse_argv(int argc, char *argv[]) {
653 static const struct option options[] = {
654 { "help", no_argument, NULL, 'h' },
655 { "version", no_argument, NULL, ARG_VERSION },
656 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
657 { "host", required_argument, NULL, 'H' },
658 { "machine", required_argument, NULL, 'M' },
659 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
660 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
669 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
677 puts(PACKAGE_STRING);
678 puts(SYSTEMD_FEATURES);
689 case ARG_NO_ASK_PASSWORD:
690 arg_ask_password = false;
694 arg_transport = BUS_TRANSPORT_REMOTE;
699 arg_transport = BUS_TRANSPORT_CONTAINER;
707 assert_not_reached("Unhandled option");
714 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
716 static const struct {
724 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
726 { "status", LESS, 1, show_status },
727 { "set-locale", MORE, 2, set_locale },
728 { "list-locales", EQUAL, 1, list_locales },
729 { "set-keymap", MORE, 2, set_vconsole_keymap },
730 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
731 { "set-x11-keymap", MORE, 2, set_x11_keymap },
732 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
733 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
734 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
735 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
744 left = argc - optind;
747 /* Special rule: no arguments means "status" */
750 if (streq(argv[optind], "help")) {
755 for (i = 0; i < ELEMENTSOF(verbs); i++)
756 if (streq(argv[optind], verbs[i].verb))
759 if (i >= ELEMENTSOF(verbs)) {
760 log_error("Unknown operation %s", argv[optind]);
765 switch (verbs[i].argc_cmp) {
768 if (left != verbs[i].argc) {
769 log_error("Invalid number of arguments.");
776 if (left < verbs[i].argc) {
777 log_error("Too few arguments.");
784 if (left > verbs[i].argc) {
785 log_error("Too many arguments.");
792 assert_not_reached("Unknown comparison operator.");
795 return verbs[i].dispatch(bus, argv + optind, left);
798 int main(int argc, char*argv[]) {
799 _cleanup_bus_unref_ sd_bus *bus = NULL;
802 setlocale(LC_ALL, "");
803 log_parse_environment();
806 r = parse_argv(argc, argv);
810 r = bus_open_transport(arg_transport, arg_host, false, &bus);
812 log_error("Failed to create bus connection: %s", strerror(-r));
816 r = localectl_main(bus, argc, argv);
821 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;