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/>.
31 #include "dbus-common.h"
33 #include "spawn-polkit-agent.h"
38 #include "path-util.h"
40 static bool arg_no_pager = false;
41 static enum transport {
45 } arg_transport = TRANSPORT_NORMAL;
46 static bool arg_ask_password = true;
47 static const char *arg_host = NULL;
48 static bool arg_convert = true;
50 static void pager_open_if_enabled(void) {
58 static void polkit_agent_open_if_enabled(void) {
60 /* Open the polkit agent as a child process if necessary */
62 if (!arg_ask_password)
68 typedef struct StatusInfo {
70 const char *vconsole_keymap;
71 const char *vconsole_keymap_toggle;
72 const char *x11_layout;
73 const char *x11_model;
74 const char *x11_variant;
75 const char *x11_options;
78 static void print_status_info(StatusInfo *i) {
81 if (strv_isempty(i->locale))
82 puts(" System Locale: n/a\n");
86 printf(" System Locale: %s\n", i->locale[0]);
87 STRV_FOREACH(j, i->locale + 1)
91 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
92 if (!isempty(i->vconsole_keymap_toggle))
93 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
95 printf(" X11 Layout: %s\n", strna(i->x11_layout));
96 if (!isempty(i->x11_model))
97 printf(" X11 Model: %s\n", i->x11_model);
98 if (!isempty(i->x11_variant))
99 printf(" X11 Variant: %s\n", i->x11_variant);
100 if (!isempty(i->x11_options))
101 printf(" X11 Options: %s\n", i->x11_options);
104 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
110 switch (dbus_message_iter_get_arg_type(iter)) {
112 case DBUS_TYPE_STRING: {
115 dbus_message_iter_get_basic(iter, &s);
117 if (streq(name, "VConsoleKeymap"))
118 i->vconsole_keymap = s;
119 else if (streq(name, "VConsoleKeymapToggle"))
120 i->vconsole_keymap_toggle = s;
121 else if (streq(name, "X11Layout"))
123 else if (streq(name, "X11Model"))
125 else if (streq(name, "X11Variant"))
127 else if (streq(name, "X11Options"))
133 case DBUS_TYPE_ARRAY:
135 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
138 r = bus_parse_strv_iter(iter, &l);
142 if (streq(name, "Locale")) {
143 strv_free(i->locale);
155 static int show_status(DBusConnection *bus, char **args, unsigned n) {
156 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
157 const char *interface = "";
159 DBusMessageIter iter, sub, sub2, sub3;
164 r = bus_method_call_with_reply(
166 "org.freedesktop.locale1",
167 "/org/freedesktop/locale1",
168 "org.freedesktop.DBus.Properties",
172 DBUS_TYPE_STRING, &interface,
177 if (!dbus_message_iter_init(reply, &iter) ||
178 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
179 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
180 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 list_locales(DBusConnection *bus, char **args, unsigned n) {
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;
306 _cleanup_strv_free_ char **l = NULL;
314 locales = set_new(string_hash_func, string_compare_func);
318 fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
320 log_error("Failed to open locale archive: %m");
325 if (fstat(fd, &st) < 0) {
326 log_error("fstat() failed: %m");
331 if (!S_ISREG(st.st_mode)) {
332 log_error("Archive file is not regular");
337 if (st.st_size < (off_t) sizeof(struct locarhead)) {
338 log_error("Archive has invalid size");
343 p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
344 if (p == MAP_FAILED) {
345 log_error("Failed to map archive: %m");
350 h = (const struct locarhead *) p;
351 if (h->magic != 0xde020109 ||
352 h->namehash_offset + h->namehash_size > st.st_size ||
353 h->string_offset + h->string_size > st.st_size ||
354 h->locrectab_offset + h->locrectab_size > st.st_size ||
355 h->sumhash_offset + h->sumhash_size > st.st_size) {
356 log_error("Invalid archive file.");
361 e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
362 for (i = 0; i < h->namehash_size; i++) {
365 if (e[i].locrec_offset == 0)
368 z = strdup((char*) p + e[i].name_offset);
374 r = set_put(locales, z);
377 log_error("Failed to add locale: %s", strerror(-r));
382 l = set_get_strv(locales);
393 pager_open_if_enabled();
402 munmap((void*) p, sz);
404 set_free_free(locales);
409 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
410 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
411 dbus_bool_t interactive = true, b;
412 const char *map, *toggle_map;
418 log_error("Too many arguments.");
422 polkit_agent_open_if_enabled();
425 toggle_map = n > 2 ? args[2] : "";
428 return bus_method_call_with_reply(
430 "org.freedesktop.locale1",
431 "/org/freedesktop/locale1",
432 "org.freedesktop.locale1",
433 "SetVConsoleKeyboard",
436 DBUS_TYPE_STRING, &map,
437 DBUS_TYPE_STRING, &toggle_map,
438 DBUS_TYPE_BOOLEAN, &b,
439 DBUS_TYPE_BOOLEAN, &interactive,
443 static Set *keymaps = NULL;
447 const struct stat *sb,
449 struct FTW *ftwbuf) {
457 if (!endswith(fpath, ".map") &&
458 !endswith(fpath, ".map.gz"))
461 p = strdup(path_get_file_name(fpath));
465 e = endswith(p, ".map");
469 e = endswith(p, ".map.gz");
473 r = set_put(keymaps, p);
477 log_error("Can't add keymap: %s", strerror(-r));
485 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
486 char _cleanup_strv_free_ **l = NULL;
489 keymaps = set_new(string_hash_func, string_compare_func);
493 nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
494 nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
495 nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
497 l = set_get_strv(keymaps);
499 set_free_free(keymaps);
505 if (strv_isempty(l)) {
506 log_error("Couldn't find any console keymaps.");
512 pager_open_if_enabled();
521 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
522 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
523 dbus_bool_t interactive = true, b;
524 const char *layout, *model, *variant, *options;
530 log_error("Too many arguments.");
534 polkit_agent_open_if_enabled();
537 model = n > 2 ? args[2] : "";
538 variant = n > 3 ? args[3] : "";
539 options = n > 3 ? args[4] : "";
542 return bus_method_call_with_reply(
544 "org.freedesktop.locale1",
545 "/org/freedesktop/locale1",
546 "org.freedesktop.locale1",
550 DBUS_TYPE_STRING, &layout,
551 DBUS_TYPE_STRING, &model,
552 DBUS_TYPE_STRING, &variant,
553 DBUS_TYPE_STRING, &options,
554 DBUS_TYPE_BOOLEAN, &b,
555 DBUS_TYPE_BOOLEAN, &interactive,
559 static int help(void) {
561 printf("%s [OPTIONS...] COMMAND ...\n\n"
562 "Query or change system time and date settings.\n\n"
563 " -h --help Show this help\n"
564 " --version Show package version\n"
565 " --no-convert Don't convert keyboard mappings\n"
566 " --no-pager Do not pipe output into a pager\n"
567 " --no-ask-password Do not prompt for password\n"
568 " -H --host=[USER@]HOST Operate on remote host\n\n"
570 " status Show current locale settings\n"
571 " set-locale LOCALE... Set system locale\n"
572 " list-locales Show known locales\n"
573 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
574 " list-keymaps Show known virtual console keyboard mappings\n"
575 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
576 " Set X11 keyboard mapping\n",
577 program_invocation_short_name);
582 static int parse_argv(int argc, char *argv[]) {
591 static const struct option options[] = {
592 { "help", no_argument, NULL, 'h' },
593 { "version", no_argument, NULL, ARG_VERSION },
594 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
595 { "host", required_argument, NULL, 'H' },
596 { "privileged", no_argument, NULL, 'P' },
597 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
598 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
607 while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
616 puts(PACKAGE_STRING);
618 puts(SYSTEMD_FEATURES);
622 arg_transport = TRANSPORT_POLKIT;
626 arg_transport = TRANSPORT_SSH;
642 log_error("Unknown option code %c", c);
650 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
652 static const struct {
660 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
662 { "status", LESS, 1, show_status },
663 { "set-locale", MORE, 2, set_locale },
664 { "list-locales", EQUAL, 1, list_locales },
665 { "set-keymap", MORE, 2, set_vconsole_keymap },
666 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
667 { "set-x11-keymap", MORE, 2, set_x11_keymap },
677 left = argc - optind;
680 /* Special rule: no arguments means "status" */
683 if (streq(argv[optind], "help")) {
688 for (i = 0; i < ELEMENTSOF(verbs); i++)
689 if (streq(argv[optind], verbs[i].verb))
692 if (i >= ELEMENTSOF(verbs)) {
693 log_error("Unknown operation %s", argv[optind]);
698 switch (verbs[i].argc_cmp) {
701 if (left != verbs[i].argc) {
702 log_error("Invalid number of arguments.");
709 if (left < verbs[i].argc) {
710 log_error("Too few arguments.");
717 if (left > verbs[i].argc) {
718 log_error("Too many arguments.");
725 assert_not_reached("Unknown comparison operator.");
729 log_error("Failed to get D-Bus connection: %s", error->message);
733 return verbs[i].dispatch(bus, argv + optind, left);
736 int main(int argc, char *argv[]) {
737 int r, retval = EXIT_FAILURE;
738 DBusConnection *bus = NULL;
741 dbus_error_init(&error);
743 log_parse_environment();
746 r = parse_argv(argc, argv);
750 retval = EXIT_SUCCESS;
754 if (arg_transport == TRANSPORT_NORMAL)
755 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
756 else if (arg_transport == TRANSPORT_POLKIT)
757 bus_connect_system_polkit(&bus, &error);
758 else if (arg_transport == TRANSPORT_SSH)
759 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
761 assert_not_reached("Uh, invalid transport...");
763 r = localectl_main(bus, argc, argv, &error);
764 retval = r < 0 ? EXIT_FAILURE : r;
768 dbus_connection_flush(bus);
769 dbus_connection_close(bus);
770 dbus_connection_unref(bus);
773 dbus_error_free(&error);