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;
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.");
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 z = strdup((char*) p + e[i].name_offset);
369 r = set_put(locales, z);
372 log_error("Failed to add locale: %s", strerror(-r));
381 munmap((void*) p, sz);
386 static int add_locales_from_libdir (Set *locales) {
387 DIR _cleanup_closedir_ *dir;
388 struct dirent *entry;
391 dir = opendir("/usr/lib/locale");
393 log_error("Failed to open locale directory: %m");
398 while ((entry = readdir(dir))) {
401 if (entry->d_type != DT_DIR)
404 if (ignore_file(entry->d_name))
407 z = strdup(entry->d_name);
411 r = set_put(locales, z);
416 log_error("Failed to add locale: %s", strerror(-r));
425 log_error("Failed to read locale directory: %m");
432 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
433 _cleanup_set_free_ Set *locales;
434 _cleanup_strv_free_ char **l = NULL;
437 locales = set_new(string_hash_func, string_compare_func);
441 r = add_locales_from_archive(locales);
442 if (r < 0 && r != -ENOENT)
445 r = add_locales_from_libdir(locales);
449 l = set_get_strv(locales);
455 pager_open_if_enabled();
462 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
463 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
464 dbus_bool_t interactive = true, b;
465 const char *map, *toggle_map;
471 log_error("Too many arguments.");
475 polkit_agent_open_if_enabled();
478 toggle_map = n > 2 ? args[2] : "";
481 return bus_method_call_with_reply(
483 "org.freedesktop.locale1",
484 "/org/freedesktop/locale1",
485 "org.freedesktop.locale1",
486 "SetVConsoleKeyboard",
489 DBUS_TYPE_STRING, &map,
490 DBUS_TYPE_STRING, &toggle_map,
491 DBUS_TYPE_BOOLEAN, &b,
492 DBUS_TYPE_BOOLEAN, &interactive,
496 static Set *keymaps = NULL;
500 const struct stat *sb,
502 struct FTW *ftwbuf) {
510 if (!endswith(fpath, ".map") &&
511 !endswith(fpath, ".map.gz"))
514 p = strdup(path_get_file_name(fpath));
518 e = endswith(p, ".map");
522 e = endswith(p, ".map.gz");
526 r = set_put(keymaps, p);
530 log_error("Can't add keymap: %s", strerror(-r));
538 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
539 char _cleanup_strv_free_ **l = NULL;
541 keymaps = set_new(string_hash_func, string_compare_func);
545 nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
546 nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
547 nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
549 l = set_get_strv(keymaps);
551 set_free_free(keymaps);
557 if (strv_isempty(l)) {
558 log_error("Couldn't find any console keymaps.");
564 pager_open_if_enabled();
571 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
572 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
573 dbus_bool_t interactive = true, b;
574 const char *layout, *model, *variant, *options;
580 log_error("Too many arguments.");
584 polkit_agent_open_if_enabled();
587 model = n > 2 ? args[2] : "";
588 variant = n > 3 ? args[3] : "";
589 options = n > 4 ? args[4] : "";
592 return bus_method_call_with_reply(
594 "org.freedesktop.locale1",
595 "/org/freedesktop/locale1",
596 "org.freedesktop.locale1",
600 DBUS_TYPE_STRING, &layout,
601 DBUS_TYPE_STRING, &model,
602 DBUS_TYPE_STRING, &variant,
603 DBUS_TYPE_STRING, &options,
604 DBUS_TYPE_BOOLEAN, &b,
605 DBUS_TYPE_BOOLEAN, &interactive,
609 static int help(void) {
611 printf("%s [OPTIONS...] COMMAND ...\n\n"
612 "Query or change system locale and keyboard settings.\n\n"
613 " -h --help Show this help\n"
614 " --version Show package version\n"
615 " --no-convert Don't convert keyboard mappings\n"
616 " --no-pager Do not pipe output into a pager\n"
617 " --no-ask-password Do not prompt for password\n"
618 " -H --host=[USER@]HOST Operate on remote host\n\n"
620 " status Show current locale settings\n"
621 " set-locale LOCALE... Set system locale\n"
622 " list-locales Show known locales\n"
623 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
624 " list-keymaps Show known virtual console keyboard mappings\n"
625 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
626 " Set X11 keyboard mapping\n",
627 program_invocation_short_name);
632 static int parse_argv(int argc, char *argv[]) {
641 static const struct option options[] = {
642 { "help", no_argument, NULL, 'h' },
643 { "version", no_argument, NULL, ARG_VERSION },
644 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
645 { "host", required_argument, NULL, 'H' },
646 { "privileged", no_argument, NULL, 'P' },
647 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
648 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
657 while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
666 puts(PACKAGE_STRING);
667 puts(SYSTEMD_FEATURES);
671 arg_transport = TRANSPORT_POLKIT;
675 arg_transport = TRANSPORT_SSH;
691 log_error("Unknown option code %c", c);
699 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
701 static const struct {
709 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
711 { "status", LESS, 1, show_status },
712 { "set-locale", MORE, 2, set_locale },
713 { "list-locales", EQUAL, 1, list_locales },
714 { "set-keymap", MORE, 2, set_vconsole_keymap },
715 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
716 { "set-x11-keymap", MORE, 2, set_x11_keymap },
726 left = argc - optind;
729 /* Special rule: no arguments means "status" */
732 if (streq(argv[optind], "help")) {
737 for (i = 0; i < ELEMENTSOF(verbs); i++)
738 if (streq(argv[optind], verbs[i].verb))
741 if (i >= ELEMENTSOF(verbs)) {
742 log_error("Unknown operation %s", argv[optind]);
747 switch (verbs[i].argc_cmp) {
750 if (left != verbs[i].argc) {
751 log_error("Invalid number of arguments.");
758 if (left < verbs[i].argc) {
759 log_error("Too few arguments.");
766 if (left > verbs[i].argc) {
767 log_error("Too many arguments.");
774 assert_not_reached("Unknown comparison operator.");
778 log_error("Failed to get D-Bus connection: %s", error->message);
782 return verbs[i].dispatch(bus, argv + optind, left);
785 int main(int argc, char *argv[]) {
786 int r, retval = EXIT_FAILURE;
787 DBusConnection *bus = NULL;
790 dbus_error_init(&error);
792 setlocale(LC_ALL, "");
793 log_parse_environment();
796 r = parse_argv(argc, argv);
800 retval = EXIT_SUCCESS;
804 if (arg_transport == TRANSPORT_NORMAL)
805 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
806 else if (arg_transport == TRANSPORT_POLKIT)
807 bus_connect_system_polkit(&bus, &error);
808 else if (arg_transport == TRANSPORT_SSH)
809 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
811 assert_not_reached("Uh, invalid transport...");
813 r = localectl_main(bus, argc, argv, &error);
814 retval = r < 0 ? EXIT_FAILURE : r;
818 dbus_connection_flush(bus);
819 dbus_connection_close(bus);
820 dbus_connection_unref(bus);
823 dbus_error_free(&error);