1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "alloc-util.h"
10 #include "bus-locator.h"
11 #include "bus-map-properties.h"
15 #include "format-table.h"
17 #include "locale-setup.h"
18 #include "main-func.h"
19 #include "memory-util.h"
21 #include "parse-argument.h"
22 #include "polkit-agent.h"
23 #include "pretty-print.h"
24 #include "runtime-scope.h"
25 #include "string-util.h"
27 #include "time-util.h"
30 /* Enough time for locale-gen to finish server-side (in case it is in use) */
31 #define LOCALE_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
33 static PagerFlags arg_pager_flags
= 0;
34 static bool arg_ask_password
= true;
35 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
36 static const char *arg_host
= NULL
;
37 static bool arg_convert
= true;
38 static bool arg_full
= false;
40 typedef struct StatusInfo
{
42 const char *vconsole_keymap
;
43 const char *vconsole_keymap_toggle
;
44 const char *x11_layout
;
45 const char *x11_model
;
46 const char *x11_variant
;
47 const char *x11_options
;
50 static void status_info_clear(StatusInfo
*info
) {
52 strv_free(info
->locale
);
57 static int print_status_info(StatusInfo
*i
) {
58 _cleanup_strv_free_
char **kernel_locale
= NULL
;
59 _cleanup_(table_unrefp
) Table
*table
= NULL
;
65 if (arg_transport
== BUS_TRANSPORT_LOCAL
) {
66 _cleanup_(locale_context_clear
) LocaleContext c
= {};
68 r
= locale_context_load(&c
, LOCALE_LOAD_PROC_CMDLINE
);
70 return log_error_errno(r
, "Failed to read /proc/cmdline: %m");
72 r
= locale_context_build_env(&c
, &kernel_locale
, NULL
);
74 return log_error_errno(r
, "Failed to build locale settings from kernel command line: %m");
77 table
= table_new_vertical();
82 table_set_width(table
, 0);
84 assert_se(cell
= table_get_cell(table
, 0, 0));
85 (void) table_set_ellipsize_percent(table
, cell
, 100);
87 table_set_ersatz_string(table
, TABLE_ERSATZ_UNSET
);
89 if (!strv_isempty(kernel_locale
)) {
90 log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.");
91 r
= table_add_many(table
,
92 TABLE_FIELD
, "Command Line",
93 TABLE_SET_COLOR
, ansi_highlight_yellow(),
94 TABLE_STRV
, kernel_locale
,
95 TABLE_SET_COLOR
, ansi_highlight_yellow());
97 return table_log_add_error(r
);
100 r
= table_add_many(table
,
101 TABLE_FIELD
, "System Locale",
102 TABLE_STRV
, i
->locale
,
103 TABLE_FIELD
, "VC Keymap",
104 TABLE_STRING
, i
->vconsole_keymap
);
106 return table_log_add_error(r
);
108 if (!isempty(i
->vconsole_keymap_toggle
)) {
109 r
= table_add_many(table
,
110 TABLE_FIELD
, "VC Toggle Keymap",
111 TABLE_STRING
, i
->vconsole_keymap_toggle
);
113 return table_log_add_error(r
);
116 r
= table_add_many(table
,
117 TABLE_FIELD
, "X11 Layout",
118 TABLE_STRING
, i
->x11_layout
);
120 return table_log_add_error(r
);
122 if (!isempty(i
->x11_model
)) {
123 r
= table_add_many(table
,
124 TABLE_FIELD
, "X11 Model",
125 TABLE_STRING
, i
->x11_model
);
127 return table_log_add_error(r
);
130 if (!isempty(i
->x11_variant
)) {
131 r
= table_add_many(table
,
132 TABLE_FIELD
, "X11 Variant",
133 TABLE_STRING
, i
->x11_variant
);
135 return table_log_add_error(r
);
138 if (!isempty(i
->x11_options
)) {
139 r
= table_add_many(table
,
140 TABLE_FIELD
, "X11 Options",
141 TABLE_STRING
, i
->x11_options
);
143 return table_log_add_error(r
);
146 r
= table_print(table
, NULL
);
148 return table_log_print_error(r
);
153 static int show_status(int argc
, char **argv
, void *userdata
) {
154 _cleanup_(status_info_clear
) StatusInfo info
= {};
155 static const struct bus_properties_map map
[] = {
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
) },
166 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
167 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
168 sd_bus
*bus
= ASSERT_PTR(userdata
);
171 r
= bus_map_all_properties(bus
,
172 "org.freedesktop.locale1",
173 "/org/freedesktop/locale1",
180 return log_error_errno(r
, "Could not get properties: %s", bus_error_message(&error
, r
));
182 return print_status_info(&info
);
185 static int set_locale(int argc
, char **argv
, void *userdata
) {
186 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
187 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
188 sd_bus
*bus
= ASSERT_PTR(userdata
);
191 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
193 r
= bus_message_new_method_call(bus
, &m
, bus_locale
, "SetLocale");
195 return bus_log_create_error(r
);
197 r
= sd_bus_message_append_strv(m
, argv
+ 1);
199 return bus_log_create_error(r
);
201 r
= sd_bus_message_append(m
, "b", arg_ask_password
);
203 return bus_log_create_error(r
);
205 /* We use a longer timeout for the method call in case localed is running locale-gen */
206 r
= sd_bus_call(bus
, m
, LOCALE_SLOW_BUS_CALL_TIMEOUT_USEC
, &error
, NULL
);
208 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
213 static int list_locales(int argc
, char **argv
, void *userdata
) {
214 _cleanup_strv_free_
char **l
= NULL
;
219 return log_error_errno(r
, "Failed to read list of locales: %m");
221 pager_open(arg_pager_flags
);
227 static int set_vconsole_keymap(int argc
, char **argv
, void *userdata
) {
228 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
229 const char *map
, *toggle_map
;
230 sd_bus
*bus
= ASSERT_PTR(userdata
);
233 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
236 toggle_map
= argc
> 2 ? argv
[2] : "";
241 "SetVConsoleKeyboard",
244 "ssbb", map
, toggle_map
, arg_convert
, arg_ask_password
);
246 return log_error_errno(r
, "Failed to set keymap: %s", bus_error_message(&error
, r
));
251 static int list_vconsole_keymaps(int argc
, char **argv
, void *userdata
) {
252 _cleanup_strv_free_
char **l
= NULL
;
257 return log_error_errno(r
, "Failed to read list of keymaps: %m");
259 pager_open(arg_pager_flags
);
266 static int set_x11_keymap(int argc
, char **argv
, void *userdata
) {
267 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
268 const char *layout
, *model
, *variant
, *options
;
269 sd_bus
*bus
= userdata
;
272 (void) polkit_agent_open_if_enabled(arg_transport
, arg_ask_password
);
275 model
= argc
> 2 ? argv
[2] : "";
276 variant
= argc
> 3 ? argv
[3] : "";
277 options
= argc
> 4 ? argv
[4] : "";
285 "ssssbb", layout
, model
, variant
, options
,
286 arg_convert
, arg_ask_password
);
288 return log_error_errno(r
, "Failed to set keymap: %s", bus_error_message(&error
, r
));
293 static int list_x11_keymaps(int argc
, char **argv
, void *userdata
) {
294 _cleanup_fclose_
FILE *f
= NULL
;
295 _cleanup_strv_free_
char **list
= NULL
;
302 } state
= NONE
, look_for
;
305 f
= fopen("/usr/share/X11/xkb/rules/base.lst", "re");
307 return log_error_errno(errno
, "Failed to open keyboard mapping list. %m");
309 if (streq(argv
[0], "list-x11-keymap-models"))
311 else if (streq(argv
[0], "list-x11-keymap-layouts"))
313 else if (streq(argv
[0], "list-x11-keymap-variants"))
315 else if (streq(argv
[0], "list-x11-keymap-options"))
318 assert_not_reached();
321 _cleanup_free_
char *line
= NULL
;
324 r
= read_stripped_line(f
, LONG_LINE_MAX
, &line
);
326 return log_error_errno(r
, "Failed to read keyboard mapping list: %m");
333 if (line
[0] == '!') {
334 if (startswith(line
, "! model"))
336 else if (startswith(line
, "! layout"))
338 else if (startswith(line
, "! variant"))
340 else if (startswith(line
, "! option"))
348 if (state
!= look_for
)
351 w
= line
+ strcspn(line
, WHITESPACE
);
361 w
+= strspn(w
, WHITESPACE
);
369 if (!streq(w
, argv
[1]))
374 if (strv_consume(&list
, TAKE_PTR(line
)) < 0)
378 if (strv_isempty(list
))
379 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
),
380 "Couldn't find any entries.");
382 strv_sort_uniq(list
);
384 pager_open(arg_pager_flags
);
390 static int help(void) {
391 _cleanup_free_
char *link
= NULL
;
394 r
= terminal_urlify_man("localectl", "1", &link
);
398 printf("%s [OPTIONS...] COMMAND ...\n\n"
399 "%sQuery or change system locale and keyboard settings.%s\n"
401 " status Show current locale settings\n"
402 " set-locale LOCALE... Set system locale\n"
403 " list-locales Show known locales\n"
404 " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n"
405 " list-keymaps Show known virtual console keyboard mappings\n"
406 " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
407 " Set X11 and console keyboard mappings\n"
408 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
409 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
410 " list-x11-keymap-variants [LAYOUT]\n"
411 " Show known X11 keyboard mapping variants\n"
412 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
414 " -h --help Show this help\n"
415 " --version Show package version\n"
416 " -l --full Do not ellipsize output\n"
417 " --no-pager Do not pipe output into a pager\n"
418 " --no-ask-password Do not prompt for password\n"
419 " -H --host=[USER@]HOST Operate on remote host\n"
420 " -M --machine=CONTAINER Operate on local container\n"
421 " --no-convert Don't convert keyboard mappings\n"
422 "\nSee the %s for details.\n",
423 program_invocation_short_name
,
431 static int verb_help(int argc
, char **argv
, void *userdata
) {
435 static int parse_argv(int argc
, char *argv
[]) {
444 static const struct option options
[] = {
445 { "help", no_argument
, NULL
, 'h' },
446 { "version", no_argument
, NULL
, ARG_VERSION
},
447 { "full", no_argument
, NULL
, 'l' },
448 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
449 { "host", required_argument
, NULL
, 'H' },
450 { "machine", required_argument
, NULL
, 'M' },
451 { "no-ask-password", no_argument
, NULL
, ARG_NO_ASK_PASSWORD
},
452 { "no-convert", no_argument
, NULL
, ARG_NO_CONVERT
},
461 while ((c
= getopt_long(argc
, argv
, "hlH:M:", options
, NULL
)) >= 0)
480 arg_pager_flags
|= PAGER_DISABLE
;
483 case ARG_NO_ASK_PASSWORD
:
484 arg_ask_password
= false;
488 arg_transport
= BUS_TRANSPORT_REMOTE
;
493 r
= parse_machine_argument(optarg
, &arg_host
, &arg_transport
);
502 assert_not_reached();
508 static int localectl_main(sd_bus
*bus
, int argc
, char *argv
[]) {
510 static const Verb verbs
[] = {
511 { "status", VERB_ANY
, 1, VERB_DEFAULT
, show_status
},
512 { "set-locale", 2, VERB_ANY
, 0, set_locale
},
513 { "list-locales", VERB_ANY
, 1, 0, list_locales
},
514 { "set-keymap", 2, 3, 0, set_vconsole_keymap
},
515 { "list-keymaps", VERB_ANY
, 1, 0, list_vconsole_keymaps
},
516 { "set-x11-keymap", 2, 5, 0, set_x11_keymap
},
517 { "list-x11-keymap-models", VERB_ANY
, 1, 0, list_x11_keymaps
},
518 { "list-x11-keymap-layouts", VERB_ANY
, 1, 0, list_x11_keymaps
},
519 { "list-x11-keymap-variants", VERB_ANY
, 2, 0, list_x11_keymaps
},
520 { "list-x11-keymap-options", VERB_ANY
, 1, 0, list_x11_keymaps
},
521 { "help", VERB_ANY
, VERB_ANY
, 0, verb_help
}, /* Not documented, but supported since it is created. */
525 return dispatch_verb(argc
, argv
, verbs
, bus
);
528 static int run(int argc
, char *argv
[]) {
529 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
532 setlocale(LC_ALL
, "");
535 r
= parse_argv(argc
, argv
);
539 r
= bus_connect_transport(arg_transport
, arg_host
, RUNTIME_SCOPE_SYSTEM
, &bus
);
541 return bus_log_connect_error(r
, arg_transport
, RUNTIME_SCOPE_SYSTEM
);
543 return localectl_main(bus
, argc
, argv
);
546 DEFINE_MAIN_FUNCTION(run
);