1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
32 #include "dbus-common.h"
34 #include "spawn-polkit-agent.h"
39 #include "path-util.h"
41 static bool arg_no_pager = false;
42 static enum transport {
46 } arg_transport = TRANSPORT_NORMAL;
47 static bool arg_ask_password = true;
48 static const char *arg_host = NULL;
49 static bool arg_convert = true;
51 static void pager_open_if_enabled(void) {
59 static void polkit_agent_open_if_enabled(void) {
61 /* Open the polkit agent as a child process if necessary */
63 if (!arg_ask_password)
69 typedef struct StatusInfo {
71 const char *vconsole_keymap;
72 const char *vconsole_keymap_toggle;
73 const char *x11_layout;
74 const char *x11_model;
75 const char *x11_variant;
76 const char *x11_options;
79 static void print_status_info(StatusInfo *i) {
82 if (strv_isempty(i->locale))
83 puts(" System Locale: n/a\n");
87 printf(" System Locale: %s\n", i->locale[0]);
88 STRV_FOREACH(j, i->locale + 1)
92 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
93 if (!isempty(i->vconsole_keymap_toggle))
94 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
96 printf(" X11 Layout: %s\n", strna(i->x11_layout));
97 if (!isempty(i->x11_model))
98 printf(" X11 Model: %s\n", i->x11_model);
99 if (!isempty(i->x11_variant))
100 printf(" X11 Variant: %s\n", i->x11_variant);
101 if (!isempty(i->x11_options))
102 printf(" X11 Options: %s\n", i->x11_options);
105 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
111 switch (dbus_message_iter_get_arg_type(iter)) {
113 case DBUS_TYPE_STRING: {
116 dbus_message_iter_get_basic(iter, &s);
118 if (streq(name, "VConsoleKeymap"))
119 i->vconsole_keymap = s;
120 else if (streq(name, "VConsoleKeymapToggle"))
121 i->vconsole_keymap_toggle = s;
122 else if (streq(name, "X11Layout"))
124 else if (streq(name, "X11Model"))
126 else if (streq(name, "X11Variant"))
128 else if (streq(name, "X11Options"))
134 case DBUS_TYPE_ARRAY:
136 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
139 r = bus_parse_strv_iter(iter, &l);
143 if (streq(name, "Locale")) {
144 strv_free(i->locale);
156 static int show_status(DBusConnection *bus, char **args, unsigned n) {
157 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
158 const char *interface = "";
160 DBusMessageIter iter, sub, sub2, sub3;
161 StatusInfo info = {};
165 r = bus_method_call_with_reply(
167 "org.freedesktop.locale1",
168 "/org/freedesktop/locale1",
169 "org.freedesktop.DBus.Properties",
173 DBUS_TYPE_STRING, &interface,
178 if (!dbus_message_iter_init(reply, &iter) ||
179 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
180 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
181 log_error("Failed to parse reply.");
185 dbus_message_iter_recurse(&iter, &sub);
187 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
190 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
191 log_error("Failed to parse reply.");
195 dbus_message_iter_recurse(&sub, &sub2);
197 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
198 log_error("Failed to parse reply.");
202 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
203 log_error("Failed to parse reply.");
207 dbus_message_iter_recurse(&sub2, &sub3);
209 r = status_property(name, &sub3, &info);
211 log_error("Failed to parse reply.");
215 dbus_message_iter_next(&sub);
218 print_status_info(&info);
219 strv_free(info.locale);
223 static int set_locale(DBusConnection *bus, char **args, unsigned n) {
224 _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
225 dbus_bool_t interactive = true;
227 DBusMessageIter iter;
233 dbus_error_init(&error);
235 polkit_agent_open_if_enabled();
237 m = dbus_message_new_method_call(
238 "org.freedesktop.locale1",
239 "/org/freedesktop/locale1",
240 "org.freedesktop.locale1",
245 dbus_message_iter_init_append(m, &iter);
247 r = bus_append_strv_iter(&iter, args + 1);
251 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &interactive))
254 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
256 log_error("Failed to issue method call: %s", bus_error_message(&error));
264 dbus_error_free(&error);
268 static int add_locales_from_archive(Set *locales) {
269 /* Stolen from glibc... */
275 /* Name hash table. */
276 uint32_t namehash_offset;
277 uint32_t namehash_used;
278 uint32_t namehash_size;
280 uint32_t string_offset;
281 uint32_t string_used;
282 uint32_t string_size;
283 /* Table with locale records. */
284 uint32_t locrectab_offset;
285 uint32_t locrectab_used;
286 uint32_t locrectab_size;
287 /* MD5 sum hash table. */
288 uint32_t sumhash_offset;
289 uint32_t sumhash_used;
290 uint32_t sumhash_size;
294 /* Hash value of the name. */
296 /* Offset of the name in the string table. */
297 uint32_t name_offset;
298 /* Offset of the locale record. */
299 uint32_t locrec_offset;
302 const struct locarhead *h;
303 const struct namehashent *e;
304 const void *p = MAP_FAILED;
305 _cleanup_close_ int fd = -1;
311 fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
314 log_error("Failed to open locale archive: %m");
319 if (fstat(fd, &st) < 0) {
320 log_error("fstat() failed: %m");
325 if (!S_ISREG(st.st_mode)) {
326 log_error("Archive file is not regular");
331 if (st.st_size < (off_t) sizeof(struct locarhead)) {
332 log_error("Archive has invalid size");
337 p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
338 if (p == MAP_FAILED) {
339 log_error("Failed to map archive: %m");
344 h = (const struct locarhead *) p;
345 if (h->magic != 0xde020109 ||
346 h->namehash_offset + h->namehash_size > st.st_size ||
347 h->string_offset + h->string_size > st.st_size ||
348 h->locrectab_offset + h->locrectab_size > st.st_size ||
349 h->sumhash_offset + h->sumhash_size > st.st_size) {
350 log_error("Invalid archive file.");
355 e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
356 for (i = 0; i < h->namehash_size; i++) {
359 if (e[i].locrec_offset == 0)
362 z = strdup((char*) p + e[i].name_offset);
368 r = set_put(locales, z);
371 log_error("Failed to add locale: %s", strerror(-r));
380 munmap((void*) p, sz);
385 static int add_locales_from_libdir (Set *locales) {
386 DIR _cleanup_closedir_ *dir;
387 struct dirent *entry;
390 dir = opendir("/usr/lib/locale");
392 log_error("Failed to open locale directory: %m");
397 while ((entry = readdir(dir))) {
400 if (entry->d_type != DT_DIR)
403 if (ignore_file(entry->d_name))
406 z = strdup(entry->d_name);
410 r = set_put(locales, z);
415 log_error("Failed to add locale: %s", strerror(-r));
424 log_error("Failed to read locale directory: %m");
431 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
432 _cleanup_set_free_ Set *locales;
433 _cleanup_strv_free_ char **l = NULL;
436 locales = set_new(string_hash_func, string_compare_func);
440 r = add_locales_from_archive(locales);
441 if (r < 0 && r != -ENOENT)
444 r = add_locales_from_libdir(locales);
448 l = set_get_strv(locales);
454 pager_open_if_enabled();
461 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
462 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
463 dbus_bool_t interactive = true, b;
464 const char *map, *toggle_map;
470 log_error("Too many arguments.");
474 polkit_agent_open_if_enabled();
477 toggle_map = n > 2 ? args[2] : "";
480 return bus_method_call_with_reply(
482 "org.freedesktop.locale1",
483 "/org/freedesktop/locale1",
484 "org.freedesktop.locale1",
485 "SetVConsoleKeyboard",
488 DBUS_TYPE_STRING, &map,
489 DBUS_TYPE_STRING, &toggle_map,
490 DBUS_TYPE_BOOLEAN, &b,
491 DBUS_TYPE_BOOLEAN, &interactive,
495 static Set *keymaps = NULL;
499 const struct stat *sb,
501 struct FTW *ftwbuf) {
509 if (!endswith(fpath, ".map") &&
510 !endswith(fpath, ".map.gz"))
513 p = strdup(path_get_file_name(fpath));
517 e = endswith(p, ".map");
521 e = endswith(p, ".map.gz");
525 r = set_put(keymaps, p);
529 log_error("Can't add keymap: %s", strerror(-r));
537 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
538 char _cleanup_strv_free_ **l = NULL;
540 keymaps = set_new(string_hash_func, string_compare_func);
544 nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
545 nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
546 nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
548 l = set_get_strv(keymaps);
550 set_free_free(keymaps);
556 if (strv_isempty(l)) {
557 log_error("Couldn't find any console keymaps.");
563 pager_open_if_enabled();
570 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
571 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
572 dbus_bool_t interactive = true, b;
573 const char *layout, *model, *variant, *options;
579 log_error("Too many arguments.");
583 polkit_agent_open_if_enabled();
586 model = n > 2 ? args[2] : "";
587 variant = n > 3 ? args[3] : "";
588 options = n > 4 ? args[4] : "";
591 return bus_method_call_with_reply(
593 "org.freedesktop.locale1",
594 "/org/freedesktop/locale1",
595 "org.freedesktop.locale1",
599 DBUS_TYPE_STRING, &layout,
600 DBUS_TYPE_STRING, &model,
601 DBUS_TYPE_STRING, &variant,
602 DBUS_TYPE_STRING, &options,
603 DBUS_TYPE_BOOLEAN, &b,
604 DBUS_TYPE_BOOLEAN, &interactive,
608 static int list_x11_keymaps(DBusConnection *bus, char **args, unsigned n) {
609 _cleanup_fclose_ FILE *f = NULL;
610 char _cleanup_strv_free_ **list = NULL;
618 } state = NONE, look_for;
622 log_error("Too many arguments.");
626 f = fopen("/usr/share/X11/xkb/rules/xorg.lst", "re");
628 log_error("Failed to open keyboard mapping list. %m");
632 if (streq(args[0], "list-x11-keymap-models"))
634 else if (streq(args[0], "list-x11-keymap-layouts"))
636 else if (streq(args[0], "list-x11-keymap-variants"))
638 else if (streq(args[0], "list-x11-keymap-options"))
641 assert_not_reached("Wrong parameter");
643 FOREACH_LINE(line, f, break) {
652 if (startswith(l, "! model"))
654 else if (startswith(l, "! layout"))
656 else if (startswith(l, "! variant"))
658 else if (startswith(l, "! option"))
666 if (state != look_for)
669 w = l + strcspn(l, WHITESPACE);
679 w += strspn(w, WHITESPACE);
687 if (!streq(w, args[1]))
692 r = strv_extend(&list, l);
697 if (strv_isempty(list)) {
698 log_error("Couldn't find any entries.");
705 pager_open_if_enabled();
711 static int help(void) {
713 printf("%s [OPTIONS...] COMMAND ...\n\n"
714 "Query or change system locale and keyboard settings.\n\n"
715 " -h --help Show this help\n"
716 " --version Show package version\n"
717 " --no-convert Don't convert keyboard mappings\n"
718 " --no-pager Do not pipe output into a pager\n"
719 " --no-ask-password Do not prompt for password\n"
720 " -H --host=[USER@]HOST Operate on remote host\n\n"
722 " status Show current locale settings\n"
723 " set-locale LOCALE... Set system locale\n"
724 " list-locales Show known locales\n"
725 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
726 " list-keymaps Show known virtual console keyboard mappings\n"
727 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
728 " Set X11 keyboard mapping\n"
729 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
730 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
731 " list-x11-keymap-variants [LAYOUT]\n"
732 " Show known X11 keyboard mapping variants\n"
733 " list-x11-keymap-options Show known X11 keyboard mapping options\n",
734 program_invocation_short_name);
739 static int parse_argv(int argc, char *argv[]) {
748 static const struct option options[] = {
749 { "help", no_argument, NULL, 'h' },
750 { "version", no_argument, NULL, ARG_VERSION },
751 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
752 { "host", required_argument, NULL, 'H' },
753 { "privileged", no_argument, NULL, 'P' },
754 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
755 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
764 while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
773 puts(PACKAGE_STRING);
774 puts(SYSTEMD_FEATURES);
778 arg_transport = TRANSPORT_POLKIT;
782 arg_transport = TRANSPORT_SSH;
798 log_error("Unknown option code %c", c);
806 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
808 static const struct {
816 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
818 { "status", LESS, 1, show_status },
819 { "set-locale", MORE, 2, set_locale },
820 { "list-locales", EQUAL, 1, list_locales },
821 { "set-keymap", MORE, 2, set_vconsole_keymap },
822 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
823 { "set-x11-keymap", MORE, 2, set_x11_keymap },
824 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
825 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
826 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
827 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
837 left = argc - optind;
840 /* Special rule: no arguments means "status" */
843 if (streq(argv[optind], "help")) {
848 for (i = 0; i < ELEMENTSOF(verbs); i++)
849 if (streq(argv[optind], verbs[i].verb))
852 if (i >= ELEMENTSOF(verbs)) {
853 log_error("Unknown operation %s", argv[optind]);
858 switch (verbs[i].argc_cmp) {
861 if (left != verbs[i].argc) {
862 log_error("Invalid number of arguments.");
869 if (left < verbs[i].argc) {
870 log_error("Too few arguments.");
877 if (left > verbs[i].argc) {
878 log_error("Too many arguments.");
885 assert_not_reached("Unknown comparison operator.");
889 log_error("Failed to get D-Bus connection: %s", error->message);
893 return verbs[i].dispatch(bus, argv + optind, left);
896 int main(int argc, char *argv[]) {
897 int r, retval = EXIT_FAILURE;
898 DBusConnection *bus = NULL;
901 dbus_error_init(&error);
903 setlocale(LC_ALL, "");
904 log_parse_environment();
907 r = parse_argv(argc, argv);
911 retval = EXIT_SUCCESS;
915 if (arg_transport == TRANSPORT_NORMAL)
916 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
917 else if (arg_transport == TRANSPORT_POLKIT)
918 bus_connect_system_polkit(&bus, &error);
919 else if (arg_transport == TRANSPORT_SSH)
920 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
922 assert_not_reached("Uh, invalid transport...");
924 r = localectl_main(bus, argc, argv, &error);
925 retval = r < 0 ? EXIT_FAILURE : r;
929 dbus_connection_flush(bus);
930 dbus_connection_close(bus);
931 dbus_connection_unref(bus);
934 dbus_error_free(&error);