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 char *arg_host
= NULL
;
50 static char *arg_user
= 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 */
65 if (!arg_ask_password
)
71 typedef struct StatusInfo
{
73 const char *vconsole_keymap
;
74 const char *vconsole_keymap_toggle
;
75 const char *x11_layout
;
76 const char *x11_model
;
77 const char *x11_variant
;
78 const char *x11_options
;
81 static void print_status_info(StatusInfo
*i
) {
84 if (strv_isempty(i
->locale
))
85 puts(" System Locale: n/a\n");
89 printf(" System Locale: %s\n", i
->locale
[0]);
90 STRV_FOREACH(j
, i
->locale
+ 1)
94 printf(" VC Keymap: %s\n", strna(i
->vconsole_keymap
));
95 if (!isempty(i
->vconsole_keymap_toggle
))
96 printf("VC Toggle Keymap: %s\n", i
->vconsole_keymap_toggle
);
98 printf(" X11 Layout: %s\n", strna(i
->x11_layout
));
99 if (!isempty(i
->x11_model
))
100 printf(" X11 Model: %s\n", i
->x11_model
);
101 if (!isempty(i
->x11_variant
))
102 printf(" X11 Variant: %s\n", i
->x11_variant
);
103 if (!isempty(i
->x11_options
))
104 printf(" X11 Options: %s\n", i
->x11_options
);
107 static int status_property(const char *name
, DBusMessageIter
*iter
, StatusInfo
*i
) {
113 switch (dbus_message_iter_get_arg_type(iter
)) {
115 case DBUS_TYPE_STRING
: {
118 dbus_message_iter_get_basic(iter
, &s
);
120 if (streq(name
, "VConsoleKeymap"))
121 i
->vconsole_keymap
= s
;
122 else if (streq(name
, "VConsoleKeymapToggle"))
123 i
->vconsole_keymap_toggle
= s
;
124 else if (streq(name
, "X11Layout"))
126 else if (streq(name
, "X11Model"))
128 else if (streq(name
, "X11Variant"))
130 else if (streq(name
, "X11Options"))
136 case DBUS_TYPE_ARRAY
:
138 if (dbus_message_iter_get_element_type(iter
) == DBUS_TYPE_STRING
) {
141 r
= bus_parse_strv_iter(iter
, &l
);
145 if (streq(name
, "Locale")) {
146 strv_free(i
->locale
);
158 static int show_status(DBusConnection
*bus
, char **args
, unsigned n
) {
159 _cleanup_dbus_message_unref_ DBusMessage
*reply
= NULL
;
160 const char *interface
= "";
162 DBusMessageIter iter
, sub
, sub2
, sub3
;
163 StatusInfo info
= {};
167 r
= bus_method_call_with_reply(
169 "org.freedesktop.locale1",
170 "/org/freedesktop/locale1",
171 "org.freedesktop.DBus.Properties",
175 DBUS_TYPE_STRING
, &interface
,
180 if (!dbus_message_iter_init(reply
, &iter
) ||
181 dbus_message_iter_get_arg_type(&iter
) != DBUS_TYPE_ARRAY
||
182 dbus_message_iter_get_element_type(&iter
) != DBUS_TYPE_DICT_ENTRY
) {
183 log_error("Failed to parse reply.");
187 dbus_message_iter_recurse(&iter
, &sub
);
189 while (dbus_message_iter_get_arg_type(&sub
) != DBUS_TYPE_INVALID
) {
192 if (dbus_message_iter_get_arg_type(&sub
) != DBUS_TYPE_DICT_ENTRY
) {
193 log_error("Failed to parse reply.");
197 dbus_message_iter_recurse(&sub
, &sub2
);
199 if (bus_iter_get_basic_and_next(&sub2
, DBUS_TYPE_STRING
, &name
, true) < 0) {
200 log_error("Failed to parse reply.");
204 if (dbus_message_iter_get_arg_type(&sub2
) != DBUS_TYPE_VARIANT
) {
205 log_error("Failed to parse reply.");
209 dbus_message_iter_recurse(&sub2
, &sub3
);
211 r
= status_property(name
, &sub3
, &info
);
213 log_error("Failed to parse reply.");
217 dbus_message_iter_next(&sub
);
220 print_status_info(&info
);
221 strv_free(info
.locale
);
225 static int set_locale(DBusConnection
*bus
, char **args
, unsigned n
) {
226 _cleanup_dbus_message_unref_ DBusMessage
*m
= NULL
, *reply
= NULL
;
227 dbus_bool_t interactive
= arg_ask_password
;
229 DBusMessageIter iter
;
235 dbus_error_init(&error
);
237 polkit_agent_open_if_enabled();
239 m
= dbus_message_new_method_call(
240 "org.freedesktop.locale1",
241 "/org/freedesktop/locale1",
242 "org.freedesktop.locale1",
247 dbus_message_iter_init_append(m
, &iter
);
249 r
= bus_append_strv_iter(&iter
, args
+ 1);
253 if (!dbus_message_iter_append_basic(&iter
, DBUS_TYPE_BOOLEAN
, &interactive
))
256 reply
= dbus_connection_send_with_reply_and_block(bus
, m
, -1, &error
);
258 log_error("Failed to issue method call: %s", bus_error_message(&error
));
266 dbus_error_free(&error
);
270 static int add_locales_from_archive(Set
*locales
) {
271 /* Stolen from glibc... */
277 /* Name hash table. */
278 uint32_t namehash_offset
;
279 uint32_t namehash_used
;
280 uint32_t namehash_size
;
282 uint32_t string_offset
;
283 uint32_t string_used
;
284 uint32_t string_size
;
285 /* Table with locale records. */
286 uint32_t locrectab_offset
;
287 uint32_t locrectab_used
;
288 uint32_t locrectab_size
;
289 /* MD5 sum hash table. */
290 uint32_t sumhash_offset
;
291 uint32_t sumhash_used
;
292 uint32_t sumhash_size
;
296 /* Hash value of the name. */
298 /* Offset of the name in the string table. */
299 uint32_t name_offset
;
300 /* Offset of the locale record. */
301 uint32_t locrec_offset
;
304 const struct locarhead
*h
;
305 const struct namehashent
*e
;
306 const void *p
= MAP_FAILED
;
307 _cleanup_close_
int fd
= -1;
313 fd
= open("/usr/lib/locale/locale-archive", O_RDONLY
|O_NOCTTY
|O_CLOEXEC
);
316 log_error("Failed to open locale archive: %m");
321 if (fstat(fd
, &st
) < 0) {
322 log_error("fstat() failed: %m");
327 if (!S_ISREG(st
.st_mode
)) {
328 log_error("Archive file is not regular");
333 if (st
.st_size
< (off_t
) sizeof(struct locarhead
)) {
334 log_error("Archive has invalid size");
339 p
= mmap(NULL
, st
.st_size
, PROT_READ
, MAP_SHARED
, fd
, 0);
340 if (p
== MAP_FAILED
) {
341 log_error("Failed to map archive: %m");
346 h
= (const struct locarhead
*) p
;
347 if (h
->magic
!= 0xde020109 ||
348 h
->namehash_offset
+ h
->namehash_size
> st
.st_size
||
349 h
->string_offset
+ h
->string_size
> st
.st_size
||
350 h
->locrectab_offset
+ h
->locrectab_size
> st
.st_size
||
351 h
->sumhash_offset
+ h
->sumhash_size
> st
.st_size
) {
352 log_error("Invalid archive file.");
357 e
= (const struct namehashent
*) ((const uint8_t*) p
+ h
->namehash_offset
);
358 for (i
= 0; i
< h
->namehash_size
; i
++) {
361 if (e
[i
].locrec_offset
== 0)
364 if (!utf8_is_valid((char*) p
+ e
[i
].name_offset
))
367 z
= strdup((char*) p
+ e
[i
].name_offset
);
373 r
= set_consume(locales
, z
);
375 log_error("Failed to add locale: %s", strerror(-r
));
384 munmap((void*) p
, sz
);
389 static int add_locales_from_libdir (Set
*locales
) {
390 _cleanup_closedir_
DIR *dir
;
391 struct dirent
*entry
;
394 dir
= opendir("/usr/lib/locale");
396 log_error("Failed to open locale directory: %m");
401 while ((entry
= readdir(dir
))) {
404 if (entry
->d_type
!= DT_DIR
)
407 if (ignore_file(entry
->d_name
))
410 z
= strdup(entry
->d_name
);
414 r
= set_consume(locales
, z
);
415 if (r
< 0 && r
!= -EEXIST
) {
416 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
= arg_ask_password
, 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_consume(keymaps
, p
);
526 if (r
< 0 && r
!= -EEXIST
) {
527 log_error("Can't add keymap: %s", strerror(-r
));
534 static int list_vconsole_keymaps(DBusConnection
*bus
, char **args
, unsigned n
) {
535 _cleanup_strv_free_
char **l
= NULL
;
537 keymaps
= set_new(string_hash_func
, string_compare_func
);
541 nftw("/usr/share/keymaps/", nftw_cb
, 20, FTW_MOUNT
|FTW_PHYS
);
542 nftw("/usr/share/kbd/keymaps/", nftw_cb
, 20, FTW_MOUNT
|FTW_PHYS
);
543 nftw("/usr/lib/kbd/keymaps/", nftw_cb
, 20, FTW_MOUNT
|FTW_PHYS
);
544 nftw("/lib/kbd/keymaps/", nftw_cb
, 20, FTW_MOUNT
|FTW_PHYS
);
546 l
= set_get_strv(keymaps
);
548 set_free_free(keymaps
);
554 if (strv_isempty(l
)) {
555 log_error("Couldn't find any console keymaps.");
561 pager_open_if_enabled();
568 static int set_x11_keymap(DBusConnection
*bus
, char **args
, unsigned n
) {
569 _cleanup_dbus_message_unref_ DBusMessage
*reply
= NULL
;
570 dbus_bool_t interactive
= arg_ask_password
, b
;
571 const char *layout
, *model
, *variant
, *options
;
577 log_error("Too many arguments.");
581 polkit_agent_open_if_enabled();
584 model
= n
> 2 ? args
[2] : "";
585 variant
= n
> 3 ? args
[3] : "";
586 options
= n
> 4 ? args
[4] : "";
589 return bus_method_call_with_reply(
591 "org.freedesktop.locale1",
592 "/org/freedesktop/locale1",
593 "org.freedesktop.locale1",
597 DBUS_TYPE_STRING
, &layout
,
598 DBUS_TYPE_STRING
, &model
,
599 DBUS_TYPE_STRING
, &variant
,
600 DBUS_TYPE_STRING
, &options
,
601 DBUS_TYPE_BOOLEAN
, &b
,
602 DBUS_TYPE_BOOLEAN
, &interactive
,
606 static int list_x11_keymaps(DBusConnection
*bus
, char **args
, unsigned n
) {
607 _cleanup_fclose_
FILE *f
= NULL
;
608 _cleanup_strv_free_
char **list
= NULL
;
616 } state
= NONE
, look_for
;
620 log_error("Too many arguments.");
624 f
= fopen("/usr/share/X11/xkb/rules/base.lst", "re");
626 log_error("Failed to open keyboard mapping list. %m");
630 if (streq(args
[0], "list-x11-keymap-models"))
632 else if (streq(args
[0], "list-x11-keymap-layouts"))
634 else if (streq(args
[0], "list-x11-keymap-variants"))
636 else if (streq(args
[0], "list-x11-keymap-options"))
639 assert_not_reached("Wrong parameter");
641 FOREACH_LINE(line
, f
, break) {
650 if (startswith(l
, "! model"))
652 else if (startswith(l
, "! layout"))
654 else if (startswith(l
, "! variant"))
656 else if (startswith(l
, "! option"))
664 if (state
!= look_for
)
667 w
= l
+ strcspn(l
, WHITESPACE
);
677 w
+= strspn(w
, WHITESPACE
);
685 if (!streq(w
, args
[1]))
690 r
= strv_extend(&list
, l
);
695 if (strv_isempty(list
)) {
696 log_error("Couldn't find any entries.");
703 pager_open_if_enabled();
709 static int help(void) {
711 printf("%s [OPTIONS...] COMMAND ...\n\n"
712 "Query or change system locale and keyboard settings.\n\n"
713 " -h --help Show this help\n"
714 " --version Show package version\n"
715 " --no-convert Don't convert keyboard mappings\n"
716 " --no-pager Do not pipe output into a pager\n"
717 " -P --privileged Acquire privileges before execution\n"
718 " --no-ask-password Do not prompt for password\n"
719 " -H --host=[USER@]HOST Operate on remote host\n\n"
721 " status Show current locale settings\n"
722 " set-locale LOCALE... Set system locale\n"
723 " list-locales Show known locales\n"
724 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
725 " list-keymaps Show known virtual console keyboard mappings\n"
726 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
727 " Set X11 keyboard mapping\n"
728 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
729 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
730 " list-x11-keymap-variants [LAYOUT]\n"
731 " Show known X11 keyboard mapping variants\n"
732 " list-x11-keymap-options Show known X11 keyboard mapping options\n",
733 program_invocation_short_name
);
738 static int parse_argv(int argc
, char *argv
[]) {
747 static const struct option options
[] = {
748 { "help", no_argument
, NULL
, 'h' },
749 { "version", no_argument
, NULL
, ARG_VERSION
},
750 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
751 { "host", required_argument
, NULL
, 'H' },
752 { "privileged", no_argument
, NULL
, 'P' },
753 { "no-ask-password", no_argument
, NULL
, ARG_NO_ASK_PASSWORD
},
754 { "no-convert", no_argument
, NULL
, ARG_NO_CONVERT
},
763 while ((c
= getopt_long(argc
, argv
, "hH:P", options
, NULL
)) >= 0) {
772 puts(PACKAGE_STRING
);
773 puts(SYSTEMD_FEATURES
);
777 arg_transport
= TRANSPORT_POLKIT
;
781 arg_transport
= TRANSPORT_SSH
;
782 parse_user_at_host(optarg
, &arg_user
, &arg_host
);
793 case ARG_NO_ASK_PASSWORD
:
794 arg_ask_password
= false;
801 log_error("Unknown option code %c", c
);
809 static int localectl_main(DBusConnection
*bus
, int argc
, char *argv
[], DBusError
*error
) {
811 static const struct {
819 int (* const dispatch
)(DBusConnection
*bus
, char **args
, unsigned n
);
821 { "status", LESS
, 1, show_status
},
822 { "set-locale", MORE
, 2, set_locale
},
823 { "list-locales", EQUAL
, 1, list_locales
},
824 { "set-keymap", MORE
, 2, set_vconsole_keymap
},
825 { "list-keymaps", EQUAL
, 1, list_vconsole_keymaps
},
826 { "set-x11-keymap", MORE
, 2, set_x11_keymap
},
827 { "list-x11-keymap-models", EQUAL
, 1, list_x11_keymaps
},
828 { "list-x11-keymap-layouts", EQUAL
, 1, list_x11_keymaps
},
829 { "list-x11-keymap-variants", LESS
, 2, list_x11_keymaps
},
830 { "list-x11-keymap-options", EQUAL
, 1, list_x11_keymaps
},
840 left
= argc
- optind
;
843 /* Special rule: no arguments means "status" */
846 if (streq(argv
[optind
], "help")) {
851 for (i
= 0; i
< ELEMENTSOF(verbs
); i
++)
852 if (streq(argv
[optind
], verbs
[i
].verb
))
855 if (i
>= ELEMENTSOF(verbs
)) {
856 log_error("Unknown operation %s", argv
[optind
]);
861 switch (verbs
[i
].argc_cmp
) {
864 if (left
!= verbs
[i
].argc
) {
865 log_error("Invalid number of arguments.");
872 if (left
< verbs
[i
].argc
) {
873 log_error("Too few arguments.");
880 if (left
> verbs
[i
].argc
) {
881 log_error("Too many arguments.");
888 assert_not_reached("Unknown comparison operator.");
892 log_error("Failed to get D-Bus connection: %s", error
->message
);
896 return verbs
[i
].dispatch(bus
, argv
+ optind
, left
);
899 int main(int argc
, char *argv
[]) {
900 int r
, retval
= EXIT_FAILURE
;
901 DBusConnection
*bus
= NULL
;
904 dbus_error_init(&error
);
906 setlocale(LC_ALL
, "");
907 log_parse_environment();
910 r
= parse_argv(argc
, argv
);
914 retval
= EXIT_SUCCESS
;
918 if (arg_transport
== TRANSPORT_NORMAL
)
919 bus
= dbus_bus_get_private(DBUS_BUS_SYSTEM
, &error
);
920 else if (arg_transport
== TRANSPORT_POLKIT
)
921 bus_connect_system_polkit(&bus
, &error
);
922 else if (arg_transport
== TRANSPORT_SSH
)
923 bus_connect_system_ssh(NULL
, arg_host
, &bus
, &error
);
925 assert_not_reached("Uh, invalid transport...");
927 r
= localectl_main(bus
, argc
, argv
, &error
);
928 retval
= r
< 0 ? EXIT_FAILURE
: r
;
932 dbus_connection_flush(bus
);
933 dbus_connection_close(bus
);
934 dbus_connection_unref(bus
);
937 dbus_error_free(&error
);