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"
42 static bool arg_no_pager = false;
43 static enum transport {
47 } arg_transport = TRANSPORT_NORMAL;
48 static bool arg_ask_password = true;
49 static const char *arg_host = NULL;
50 static bool arg_convert = true;
52 static void pager_open_if_enabled(void) {
60 static void polkit_agent_open_if_enabled(void) {
62 /* Open the polkit agent as a child process if necessary */
64 if (!arg_ask_password)
70 typedef struct StatusInfo {
72 const char *vconsole_keymap;
73 const char *vconsole_keymap_toggle;
74 const char *x11_layout;
75 const char *x11_model;
76 const char *x11_variant;
77 const char *x11_options;
80 static void print_status_info(StatusInfo *i) {
83 if (strv_isempty(i->locale))
84 puts(" System Locale: n/a\n");
88 printf(" System Locale: %s\n", i->locale[0]);
89 STRV_FOREACH(j, i->locale + 1)
93 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
94 if (!isempty(i->vconsole_keymap_toggle))
95 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
97 printf(" X11 Layout: %s\n", strna(i->x11_layout));
98 if (!isempty(i->x11_model))
99 printf(" X11 Model: %s\n", i->x11_model);
100 if (!isempty(i->x11_variant))
101 printf(" X11 Variant: %s\n", i->x11_variant);
102 if (!isempty(i->x11_options))
103 printf(" X11 Options: %s\n", i->x11_options);
106 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
112 switch (dbus_message_iter_get_arg_type(iter)) {
114 case DBUS_TYPE_STRING: {
117 dbus_message_iter_get_basic(iter, &s);
119 if (streq(name, "VConsoleKeymap"))
120 i->vconsole_keymap = s;
121 else if (streq(name, "VConsoleKeymapToggle"))
122 i->vconsole_keymap_toggle = s;
123 else if (streq(name, "X11Layout"))
125 else if (streq(name, "X11Model"))
127 else if (streq(name, "X11Variant"))
129 else if (streq(name, "X11Options"))
135 case DBUS_TYPE_ARRAY:
137 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
140 r = bus_parse_strv_iter(iter, &l);
144 if (streq(name, "Locale")) {
145 strv_free(i->locale);
157 static int show_status(DBusConnection *bus, char **args, unsigned n) {
158 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
159 const char *interface = "";
161 DBusMessageIter iter, sub, sub2, sub3;
162 StatusInfo info = {};
166 r = bus_method_call_with_reply(
168 "org.freedesktop.locale1",
169 "/org/freedesktop/locale1",
170 "org.freedesktop.DBus.Properties",
174 DBUS_TYPE_STRING, &interface,
179 if (!dbus_message_iter_init(reply, &iter) ||
180 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
181 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
182 log_error("Failed to parse reply.");
186 dbus_message_iter_recurse(&iter, &sub);
188 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
191 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
192 log_error("Failed to parse reply.");
196 dbus_message_iter_recurse(&sub, &sub2);
198 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
199 log_error("Failed to parse reply.");
203 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
204 log_error("Failed to parse reply.");
208 dbus_message_iter_recurse(&sub2, &sub3);
210 r = status_property(name, &sub3, &info);
212 log_error("Failed to parse reply.");
216 dbus_message_iter_next(&sub);
219 print_status_info(&info);
220 strv_free(info.locale);
224 static int set_locale(DBusConnection *bus, char **args, unsigned n) {
225 _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
226 dbus_bool_t interactive = true;
228 DBusMessageIter iter;
234 dbus_error_init(&error);
236 polkit_agent_open_if_enabled();
238 m = dbus_message_new_method_call(
239 "org.freedesktop.locale1",
240 "/org/freedesktop/locale1",
241 "org.freedesktop.locale1",
246 dbus_message_iter_init_append(m, &iter);
248 r = bus_append_strv_iter(&iter, args + 1);
252 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &interactive))
255 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
257 log_error("Failed to issue method call: %s", bus_error_message(&error));
265 dbus_error_free(&error);
269 static int add_locales_from_archive(Set *locales) {
270 /* Stolen from glibc... */
276 /* Name hash table. */
277 uint32_t namehash_offset;
278 uint32_t namehash_used;
279 uint32_t namehash_size;
281 uint32_t string_offset;
282 uint32_t string_used;
283 uint32_t string_size;
284 /* Table with locale records. */
285 uint32_t locrectab_offset;
286 uint32_t locrectab_used;
287 uint32_t locrectab_size;
288 /* MD5 sum hash table. */
289 uint32_t sumhash_offset;
290 uint32_t sumhash_used;
291 uint32_t sumhash_size;
295 /* Hash value of the name. */
297 /* Offset of the name in the string table. */
298 uint32_t name_offset;
299 /* Offset of the locale record. */
300 uint32_t locrec_offset;
303 const struct locarhead *h;
304 const struct namehashent *e;
305 const void *p = MAP_FAILED;
306 _cleanup_close_ int fd = -1;
312 fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
315 log_error("Failed to open locale archive: %m");
320 if (fstat(fd, &st) < 0) {
321 log_error("fstat() failed: %m");
326 if (!S_ISREG(st.st_mode)) {
327 log_error("Archive file is not regular");
332 if (st.st_size < (off_t) sizeof(struct locarhead)) {
333 log_error("Archive has invalid size");
338 p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
339 if (p == MAP_FAILED) {
340 log_error("Failed to map archive: %m");
345 h = (const struct locarhead *) p;
346 if (h->magic != 0xde020109 ||
347 h->namehash_offset + h->namehash_size > st.st_size ||
348 h->string_offset + h->string_size > st.st_size ||
349 h->locrectab_offset + h->locrectab_size > st.st_size ||
350 h->sumhash_offset + h->sumhash_size > st.st_size) {
351 log_error("Invalid archive file.");
356 e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
357 for (i = 0; i < h->namehash_size; i++) {
360 if (e[i].locrec_offset == 0)
363 if (!utf8_is_valid((char*) p + e[i].name_offset))
366 z = strdup((char*) p + e[i].name_offset);
372 r = set_consume(locales, z);
374 log_error("Failed to add locale: %s", strerror(-r));
383 munmap((void*) p, sz);
388 static int add_locales_from_libdir (Set *locales) {
389 _cleanup_closedir_ DIR *dir;
390 struct dirent *entry;
393 dir = opendir("/usr/lib/locale");
395 log_error("Failed to open locale directory: %m");
400 while ((entry = readdir(dir))) {
403 if (entry->d_type != DT_DIR)
406 if (ignore_file(entry->d_name))
409 z = strdup(entry->d_name);
413 r = set_consume(locales, z);
414 if (r < 0 && r != -EEXIST) {
415 log_error("Failed to add locale: %s", strerror(-r));
423 log_error("Failed to read locale directory: %m");
430 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
431 _cleanup_set_free_ Set *locales;
432 _cleanup_strv_free_ char **l = NULL;
435 locales = set_new(string_hash_func, string_compare_func);
439 r = add_locales_from_archive(locales);
440 if (r < 0 && r != -ENOENT)
443 r = add_locales_from_libdir(locales);
447 l = set_get_strv(locales);
453 pager_open_if_enabled();
460 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
461 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
462 dbus_bool_t interactive = true, b;
463 const char *map, *toggle_map;
469 log_error("Too many arguments.");
473 polkit_agent_open_if_enabled();
476 toggle_map = n > 2 ? args[2] : "";
479 return bus_method_call_with_reply(
481 "org.freedesktop.locale1",
482 "/org/freedesktop/locale1",
483 "org.freedesktop.locale1",
484 "SetVConsoleKeyboard",
487 DBUS_TYPE_STRING, &map,
488 DBUS_TYPE_STRING, &toggle_map,
489 DBUS_TYPE_BOOLEAN, &b,
490 DBUS_TYPE_BOOLEAN, &interactive,
494 static Set *keymaps = NULL;
498 const struct stat *sb,
500 struct FTW *ftwbuf) {
508 if (!endswith(fpath, ".map") &&
509 !endswith(fpath, ".map.gz"))
512 p = strdup(path_get_file_name(fpath));
516 e = endswith(p, ".map");
520 e = endswith(p, ".map.gz");
524 r = set_consume(keymaps, p);
525 if (r < 0 && r != -EEXIST) {
526 log_error("Can't add keymap: %s", strerror(-r));
533 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
534 _cleanup_strv_free_ char **l = NULL;
536 keymaps = set_new(string_hash_func, string_compare_func);
540 nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
541 nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
542 nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
544 l = set_get_strv(keymaps);
546 set_free_free(keymaps);
552 if (strv_isempty(l)) {
553 log_error("Couldn't find any console keymaps.");
559 pager_open_if_enabled();
566 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
567 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
568 dbus_bool_t interactive = true, b;
569 const char *layout, *model, *variant, *options;
575 log_error("Too many arguments.");
579 polkit_agent_open_if_enabled();
582 model = n > 2 ? args[2] : "";
583 variant = n > 3 ? args[3] : "";
584 options = n > 4 ? args[4] : "";
587 return bus_method_call_with_reply(
589 "org.freedesktop.locale1",
590 "/org/freedesktop/locale1",
591 "org.freedesktop.locale1",
595 DBUS_TYPE_STRING, &layout,
596 DBUS_TYPE_STRING, &model,
597 DBUS_TYPE_STRING, &variant,
598 DBUS_TYPE_STRING, &options,
599 DBUS_TYPE_BOOLEAN, &b,
600 DBUS_TYPE_BOOLEAN, &interactive,
604 static int list_x11_keymaps(DBusConnection *bus, char **args, unsigned n) {
605 _cleanup_fclose_ FILE *f = NULL;
606 _cleanup_strv_free_ char **list = NULL;
614 } state = NONE, look_for;
618 log_error("Too many arguments.");
622 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
624 log_error("Failed to open keyboard mapping list. %m");
628 if (streq(args[0], "list-x11-keymap-models"))
630 else if (streq(args[0], "list-x11-keymap-layouts"))
632 else if (streq(args[0], "list-x11-keymap-variants"))
634 else if (streq(args[0], "list-x11-keymap-options"))
637 assert_not_reached("Wrong parameter");
639 FOREACH_LINE(line, f, break) {
648 if (startswith(l, "! model"))
650 else if (startswith(l, "! layout"))
652 else if (startswith(l, "! variant"))
654 else if (startswith(l, "! option"))
662 if (state != look_for)
665 w = l + strcspn(l, WHITESPACE);
675 w += strspn(w, WHITESPACE);
683 if (!streq(w, args[1]))
688 r = strv_extend(&list, l);
693 if (strv_isempty(list)) {
694 log_error("Couldn't find any entries.");
701 pager_open_if_enabled();
707 static int help(void) {
709 printf("%s [OPTIONS...] COMMAND ...\n\n"
710 "Query or change system locale and keyboard settings.\n\n"
711 " -h --help Show this help\n"
712 " --version Show package version\n"
713 " --no-convert Don't convert keyboard mappings\n"
714 " --no-pager Do not pipe output into a pager\n"
715 " --no-ask-password Do not prompt for password\n"
716 " -H --host=[USER@]HOST Operate on remote host\n\n"
718 " status Show current locale settings\n"
719 " set-locale LOCALE... Set system locale\n"
720 " list-locales Show known locales\n"
721 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
722 " list-keymaps Show known virtual console keyboard mappings\n"
723 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
724 " Set X11 keyboard mapping\n"
725 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
726 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
727 " list-x11-keymap-variants [LAYOUT]\n"
728 " Show known X11 keyboard mapping variants\n"
729 " list-x11-keymap-options Show known X11 keyboard mapping options\n",
730 program_invocation_short_name);
735 static int parse_argv(int argc, char *argv[]) {
744 static const struct option options[] = {
745 { "help", no_argument, NULL, 'h' },
746 { "version", no_argument, NULL, ARG_VERSION },
747 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
748 { "host", required_argument, NULL, 'H' },
749 { "privileged", no_argument, NULL, 'P' },
750 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
751 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
760 while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
769 puts(PACKAGE_STRING);
770 puts(SYSTEMD_FEATURES);
774 arg_transport = TRANSPORT_POLKIT;
778 arg_transport = TRANSPORT_SSH;
794 log_error("Unknown option code %c", c);
802 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
804 static const struct {
812 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
814 { "status", LESS, 1, show_status },
815 { "set-locale", MORE, 2, set_locale },
816 { "list-locales", EQUAL, 1, list_locales },
817 { "set-keymap", MORE, 2, set_vconsole_keymap },
818 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
819 { "set-x11-keymap", MORE, 2, set_x11_keymap },
820 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
821 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
822 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
823 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
833 left = argc - optind;
836 /* Special rule: no arguments means "status" */
839 if (streq(argv[optind], "help")) {
844 for (i = 0; i < ELEMENTSOF(verbs); i++)
845 if (streq(argv[optind], verbs[i].verb))
848 if (i >= ELEMENTSOF(verbs)) {
849 log_error("Unknown operation %s", argv[optind]);
854 switch (verbs[i].argc_cmp) {
857 if (left != verbs[i].argc) {
858 log_error("Invalid number of arguments.");
865 if (left < verbs[i].argc) {
866 log_error("Too few arguments.");
873 if (left > verbs[i].argc) {
874 log_error("Too many arguments.");
881 assert_not_reached("Unknown comparison operator.");
885 log_error("Failed to get D-Bus connection: %s", error->message);
889 return verbs[i].dispatch(bus, argv + optind, left);
892 int main(int argc, char *argv[]) {
893 int r, retval = EXIT_FAILURE;
894 DBusConnection *bus = NULL;
897 dbus_error_init(&error);
899 setlocale(LC_ALL, "");
900 log_parse_environment();
903 r = parse_argv(argc, argv);
907 retval = EXIT_SUCCESS;
911 if (arg_transport == TRANSPORT_NORMAL)
912 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
913 else if (arg_transport == TRANSPORT_POLKIT)
914 bus_connect_system_polkit(&bus, &error);
915 else if (arg_transport == TRANSPORT_SSH)
916 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
918 assert_not_reached("Uh, invalid transport...");
920 r = localectl_main(bus, argc, argv, &error);
921 retval = r < 0 ? EXIT_FAILURE : r;
925 dbus_connection_flush(bus);
926 dbus_connection_close(bus);
927 dbus_connection_unref(bus);
930 dbus_error_free(&error);