1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
7 Copyright 2013 Kay Sievers
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
32 #include "bus-error.h"
34 #include "spawn-polkit-agent.h"
42 #include "locale-util.h"
44 static bool arg_no_pager = false;
45 static bool arg_ask_password = true;
46 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
47 static 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 */
61 if (!arg_ask_password)
64 if (arg_transport != BUS_TRANSPORT_LOCAL)
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_overriden_variables(void) {
82 char *variables[_VARIABLE_LC_MAX] = {};
84 bool print_warning = true;
86 if (detect_container(NULL) > 0 || arg_host)
89 r = parse_env_file("/proc/cmdline", WHITESPACE,
90 "locale.LANG", &variables[VARIABLE_LANG],
91 "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE],
92 "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
93 "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
94 "locale.LC_TIME", &variables[VARIABLE_LC_TIME],
95 "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
96 "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
97 "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
98 "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER],
99 "locale.LC_NAME", &variables[VARIABLE_LC_NAME],
100 "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
101 "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
102 "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
103 "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION],
106 if (r < 0 && r != -ENOENT) {
107 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
111 for (j = 0; j < _VARIABLE_LC_MAX; j++)
114 log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n"
115 " Command Line: %s=%s", locale_variable_to_string(j), variables[j]);
117 print_warning = false;
119 log_warning(" %s=%s", locale_variable_to_string(j), variables[j]);
122 for (j = 0; j < _VARIABLE_LC_MAX; j++)
126 static void print_status_info(StatusInfo *i) {
129 if (strv_isempty(i->locale))
130 puts(" System Locale: n/a\n");
134 printf(" System Locale: %s\n", i->locale[0]);
135 STRV_FOREACH(j, i->locale + 1)
139 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
140 if (!isempty(i->vconsole_keymap_toggle))
141 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
143 printf(" X11 Layout: %s\n", strna(i->x11_layout));
144 if (!isempty(i->x11_model))
145 printf(" X11 Model: %s\n", i->x11_model);
146 if (!isempty(i->x11_variant))
147 printf(" X11 Variant: %s\n", i->x11_variant);
148 if (!isempty(i->x11_options))
149 printf(" X11 Options: %s\n", i->x11_options);
152 static int show_status(sd_bus *bus, char **args, unsigned n) {
153 StatusInfo info = {};
154 static const struct bus_properties_map map[] = {
155 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
156 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
157 { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
158 { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) },
159 { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) },
160 { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) },
161 { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) },
162 { "Locale", "as", NULL, offsetof(StatusInfo, locale) },
169 r = bus_map_all_properties(bus,
170 "org.freedesktop.locale1",
171 "/org/freedesktop/locale1",
175 log_error_errno(r, "Could not get properties: %m");
179 print_overriden_variables();
180 print_status_info(&info);
183 strv_free(info.locale);
187 static int set_locale(sd_bus *bus, char **args, unsigned n) {
188 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
189 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
195 polkit_agent_open_if_enabled();
197 r = sd_bus_message_new_method_call(
200 "org.freedesktop.locale1",
201 "/org/freedesktop/locale1",
202 "org.freedesktop.locale1",
205 return bus_log_create_error(r);
207 r = sd_bus_message_append_strv(m, args + 1);
209 return bus_log_create_error(r);
211 r = sd_bus_message_append(m, "b", arg_ask_password);
213 return bus_log_create_error(r);
215 r = sd_bus_call(bus, m, 0, &error, NULL);
217 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
224 static int list_locales(sd_bus *bus, char **args, unsigned n) {
225 _cleanup_strv_free_ char **l = NULL;
232 return log_error_errno(r, "Failed to read list of locales: %m");
234 pager_open_if_enabled();
240 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
241 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
242 const char *map, *toggle_map;
249 log_error("Too many arguments.");
253 polkit_agent_open_if_enabled();
256 toggle_map = n > 2 ? args[2] : "";
258 r = sd_bus_call_method(
260 "org.freedesktop.locale1",
261 "/org/freedesktop/locale1",
262 "org.freedesktop.locale1",
263 "SetVConsoleKeyboard",
266 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
268 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
273 static Set *keymaps = NULL;
277 const struct stat *sb,
279 struct FTW *ftwbuf) {
287 if (!endswith(fpath, ".map") &&
288 !endswith(fpath, ".map.gz"))
291 p = strdup(basename(fpath));
295 e = endswith(p, ".map");
299 e = endswith(p, ".map.gz");
303 r = set_consume(keymaps, p);
304 if (r < 0 && r != -EEXIST)
305 return log_error_errno(r, "Can't add keymap: %m");
310 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
311 _cleanup_strv_free_ char **l = NULL;
314 keymaps = set_new(&string_hash_ops);
318 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
319 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
321 l = set_get_strv(keymaps);
323 set_free_free(keymaps);
329 if (strv_isempty(l)) {
330 log_error("Couldn't find any console keymaps.");
336 pager_open_if_enabled();
343 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
344 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
345 const char *layout, *model, *variant, *options;
352 log_error("Too many arguments.");
356 polkit_agent_open_if_enabled();
359 model = n > 2 ? args[2] : "";
360 variant = n > 3 ? args[3] : "";
361 options = n > 4 ? args[4] : "";
363 r = sd_bus_call_method(
365 "org.freedesktop.locale1",
366 "/org/freedesktop/locale1",
367 "org.freedesktop.locale1",
371 "ssssbb", layout, model, variant, options,
372 arg_convert, arg_ask_password);
374 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
379 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
380 _cleanup_fclose_ FILE *f = NULL;
381 _cleanup_strv_free_ char **list = NULL;
389 } state = NONE, look_for;
393 log_error("Too many arguments.");
397 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
399 return log_error_errno(errno, "Failed to open keyboard mapping list. %m");
401 if (streq(args[0], "list-x11-keymap-models"))
403 else if (streq(args[0], "list-x11-keymap-layouts"))
405 else if (streq(args[0], "list-x11-keymap-variants"))
407 else if (streq(args[0], "list-x11-keymap-options"))
410 assert_not_reached("Wrong parameter");
412 FOREACH_LINE(line, f, break) {
421 if (startswith(l, "! model"))
423 else if (startswith(l, "! layout"))
425 else if (startswith(l, "! variant"))
427 else if (startswith(l, "! option"))
435 if (state != look_for)
438 w = l + strcspn(l, WHITESPACE);
448 w += strspn(w, WHITESPACE);
456 if (!streq(w, args[1]))
461 r = strv_extend(&list, l);
466 if (strv_isempty(list)) {
467 log_error("Couldn't find any entries.");
474 pager_open_if_enabled();
480 static void help(void) {
481 printf("%s [OPTIONS...] COMMAND ...\n\n"
482 "Query or change system locale and keyboard settings.\n\n"
483 " -h --help Show this help\n"
484 " --version Show package version\n"
485 " --no-pager Do not pipe output into a pager\n"
486 " --no-ask-password Do not prompt for password\n"
487 " -H --host=[USER@]HOST Operate on remote host\n"
488 " -M --machine=CONTAINER Operate on local container\n"
489 " --no-convert Don't convert keyboard mappings\n\n"
491 " status Show current locale settings\n"
492 " set-locale LOCALE... Set system locale\n"
493 " list-locales Show known locales\n"
494 " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n"
495 " list-keymaps Show known virtual console keyboard mappings\n"
496 " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
497 " Set X11 and console keyboard mappings\n"
498 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
499 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
500 " list-x11-keymap-variants [LAYOUT]\n"
501 " Show known X11 keyboard mapping variants\n"
502 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
503 , program_invocation_short_name);
506 static int parse_argv(int argc, char *argv[]) {
515 static const struct option options[] = {
516 { "help", no_argument, NULL, 'h' },
517 { "version", no_argument, NULL, ARG_VERSION },
518 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
519 { "host", required_argument, NULL, 'H' },
520 { "machine", required_argument, NULL, 'M' },
521 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
522 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
531 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
540 puts(PACKAGE_STRING);
541 puts(SYSTEMD_FEATURES);
552 case ARG_NO_ASK_PASSWORD:
553 arg_ask_password = false;
557 arg_transport = BUS_TRANSPORT_REMOTE;
562 arg_transport = BUS_TRANSPORT_MACHINE;
570 assert_not_reached("Unhandled option");
576 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
578 static const struct {
586 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
588 { "status", LESS, 1, show_status },
589 { "set-locale", MORE, 2, set_locale },
590 { "list-locales", EQUAL, 1, list_locales },
591 { "set-keymap", MORE, 2, set_vconsole_keymap },
592 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
593 { "set-x11-keymap", MORE, 2, set_x11_keymap },
594 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
595 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
596 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
597 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
606 left = argc - optind;
609 /* Special rule: no arguments means "status" */
612 if (streq(argv[optind], "help")) {
617 for (i = 0; i < ELEMENTSOF(verbs); i++)
618 if (streq(argv[optind], verbs[i].verb))
621 if (i >= ELEMENTSOF(verbs)) {
622 log_error("Unknown operation %s", argv[optind]);
627 switch (verbs[i].argc_cmp) {
630 if (left != verbs[i].argc) {
631 log_error("Invalid number of arguments.");
638 if (left < verbs[i].argc) {
639 log_error("Too few arguments.");
646 if (left > verbs[i].argc) {
647 log_error("Too many arguments.");
654 assert_not_reached("Unknown comparison operator.");
657 return verbs[i].dispatch(bus, argv + optind, left);
660 int main(int argc, char*argv[]) {
661 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
664 setlocale(LC_ALL, "");
665 log_parse_environment();
668 r = parse_argv(argc, argv);
672 r = bus_open_transport(arg_transport, arg_host, false, &bus);
674 log_error_errno(r, "Failed to create bus connection: %m");
678 r = localectl_main(bus, argc, argv);
683 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;