1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4 #include <stdbool.h>
5 #include <stdlib.h>
6
7 #include "sd-bus.h"
8
9 #include "bus-error.h"
10 #include "bus-locator.h"
11 #include "bus-map-properties.h"
12 #include "fd-util.h"
13 #include "fileio.h"
14 #include "kbd-util.h"
15 #include "locale-util.h"
16 #include "main-func.h"
17 #include "memory-util.h"
18 #include "pager.h"
19 #include "pretty-print.h"
20 #include "proc-cmdline.h"
21 #include "set.h"
22 #include "spawn-polkit-agent.h"
23 #include "strv.h"
24 #include "terminal-util.h"
25 #include "verbs.h"
26 #include "virt.h"
27
28 /* Enough time for locale-gen to finish server-side (in case it is in use) */
29 #define LOCALE_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
30
31 static PagerFlags arg_pager_flags = 0;
32 static bool arg_ask_password = true;
33 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
34 static const char *arg_host = NULL;
35 static bool arg_convert = true;
36
37 typedef struct StatusInfo {
38 char **locale;
39 const char *vconsole_keymap;
40 const char *vconsole_keymap_toggle;
41 const char *x11_layout;
42 const char *x11_model;
43 const char *x11_variant;
44 const char *x11_options;
45 } StatusInfo;
46
status_info_clear(StatusInfo * info)47 static void status_info_clear(StatusInfo *info) {
48 if (info) {
49 strv_free(info->locale);
50 zero(*info);
51 }
52 }
53
print_overridden_variables(void)54 static void print_overridden_variables(void) {
55 _cleanup_(locale_variables_freep) char *variables[_VARIABLE_LC_MAX] = {};
56 bool print_warning = true;
57 int r;
58
59 if (arg_transport != BUS_TRANSPORT_LOCAL)
60 return;
61
62 r = proc_cmdline_get_key_many(
63 PROC_CMDLINE_STRIP_RD_PREFIX,
64 "locale.LANG", &variables[VARIABLE_LANG],
65 "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE],
66 "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
67 "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
68 "locale.LC_TIME", &variables[VARIABLE_LC_TIME],
69 "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
70 "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
71 "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
72 "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER],
73 "locale.LC_NAME", &variables[VARIABLE_LC_NAME],
74 "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
75 "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
76 "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
77 "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION]);
78 if (r < 0 && r != -ENOENT) {
79 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
80 return;
81 }
82
83 for (LocaleVariable j = 0; j < _VARIABLE_LC_MAX; j++)
84 if (variables[j]) {
85 if (print_warning) {
86 log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n"
87 " Command Line: %s=%s", locale_variable_to_string(j), variables[j]);
88
89 print_warning = false;
90 } else
91 log_warning(" %s=%s", locale_variable_to_string(j), variables[j]);
92 }
93 }
94
print_status_info(StatusInfo * i)95 static void print_status_info(StatusInfo *i) {
96 assert(i);
97
98 if (strv_isempty(i->locale))
99 puts(" System Locale: n/a");
100 else {
101 printf(" System Locale: %s\n", i->locale[0]);
102 STRV_FOREACH(j, i->locale + 1)
103 printf(" %s\n", *j);
104 }
105
106 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
107 if (!isempty(i->vconsole_keymap_toggle))
108 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
109
110 printf(" X11 Layout: %s\n", strna(i->x11_layout));
111 if (!isempty(i->x11_model))
112 printf(" X11 Model: %s\n", i->x11_model);
113 if (!isempty(i->x11_variant))
114 printf(" X11 Variant: %s\n", i->x11_variant);
115 if (!isempty(i->x11_options))
116 printf(" X11 Options: %s\n", i->x11_options);
117 }
118
show_status(int argc,char ** argv,void * userdata)119 static int show_status(int argc, char **argv, void *userdata) {
120 _cleanup_(status_info_clear) StatusInfo info = {};
121 static const struct bus_properties_map map[] = {
122 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
123 { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
124 { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) },
125 { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) },
126 { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) },
127 { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) },
128 { "Locale", "as", NULL, offsetof(StatusInfo, locale) },
129 {}
130 };
131
132 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
133 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
134 sd_bus *bus = userdata;
135 int r;
136
137 assert(bus);
138
139 r = bus_map_all_properties(bus,
140 "org.freedesktop.locale1",
141 "/org/freedesktop/locale1",
142 map,
143 0,
144 &error,
145 &m,
146 &info);
147 if (r < 0)
148 return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
149
150 print_overridden_variables();
151 print_status_info(&info);
152
153 return r;
154 }
155
set_locale(int argc,char ** argv,void * userdata)156 static int set_locale(int argc, char **argv, void *userdata) {
157 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
158 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
159 sd_bus *bus = userdata;
160 int r;
161
162 assert(bus);
163
164 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
165
166 r = bus_message_new_method_call(bus, &m, bus_locale, "SetLocale");
167 if (r < 0)
168 return bus_log_create_error(r);
169
170 r = sd_bus_message_append_strv(m, argv + 1);
171 if (r < 0)
172 return bus_log_create_error(r);
173
174 r = sd_bus_message_append(m, "b", arg_ask_password);
175 if (r < 0)
176 return bus_log_create_error(r);
177
178 /* We use a longer timeout for the method call in case localed is running locale-gen */
179 r = sd_bus_call(bus, m, LOCALE_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
180 if (r < 0)
181 return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
182
183 return 0;
184 }
185
list_locales(int argc,char ** argv,void * userdata)186 static int list_locales(int argc, char **argv, void *userdata) {
187 _cleanup_strv_free_ char **l = NULL;
188 int r;
189
190 r = get_locales(&l);
191 if (r < 0)
192 return log_error_errno(r, "Failed to read list of locales: %m");
193
194 pager_open(arg_pager_flags);
195 strv_print(l);
196
197 return 0;
198 }
199
set_vconsole_keymap(int argc,char ** argv,void * userdata)200 static int set_vconsole_keymap(int argc, char **argv, void *userdata) {
201 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
202 const char *map, *toggle_map;
203 sd_bus *bus = userdata;
204 int r;
205
206 assert(bus);
207
208 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
209
210 map = argv[1];
211 toggle_map = argc > 2 ? argv[2] : "";
212
213 r = bus_call_method(
214 bus,
215 bus_locale,
216 "SetVConsoleKeyboard",
217 &error,
218 NULL,
219 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
220 if (r < 0)
221 return log_error_errno(r, "Failed to set keymap: %s", bus_error_message(&error, r));
222
223 return 0;
224 }
225
list_vconsole_keymaps(int argc,char ** argv,void * userdata)226 static int list_vconsole_keymaps(int argc, char **argv, void *userdata) {
227 _cleanup_strv_free_ char **l = NULL;
228 int r;
229
230 r = get_keymaps(&l);
231 if (r < 0)
232 return log_error_errno(r, "Failed to read list of keymaps: %m");
233
234 pager_open(arg_pager_flags);
235
236 strv_print(l);
237
238 return 0;
239 }
240
set_x11_keymap(int argc,char ** argv,void * userdata)241 static int set_x11_keymap(int argc, char **argv, void *userdata) {
242 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
243 const char *layout, *model, *variant, *options;
244 sd_bus *bus = userdata;
245 int r;
246
247 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
248
249 layout = argv[1];
250 model = argc > 2 ? argv[2] : "";
251 variant = argc > 3 ? argv[3] : "";
252 options = argc > 4 ? argv[4] : "";
253
254 r = bus_call_method(
255 bus,
256 bus_locale,
257 "SetX11Keyboard",
258 &error,
259 NULL,
260 "ssssbb", layout, model, variant, options,
261 arg_convert, arg_ask_password);
262 if (r < 0)
263 return log_error_errno(r, "Failed to set keymap: %s", bus_error_message(&error, r));
264
265 return 0;
266 }
267
list_x11_keymaps(int argc,char ** argv,void * userdata)268 static int list_x11_keymaps(int argc, char **argv, void *userdata) {
269 _cleanup_fclose_ FILE *f = NULL;
270 _cleanup_strv_free_ char **list = NULL;
271 enum {
272 NONE,
273 MODELS,
274 LAYOUTS,
275 VARIANTS,
276 OPTIONS
277 } state = NONE, look_for;
278 int r;
279
280 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
281 if (!f)
282 return log_error_errno(errno, "Failed to open keyboard mapping list. %m");
283
284 if (streq(argv[0], "list-x11-keymap-models"))
285 look_for = MODELS;
286 else if (streq(argv[0], "list-x11-keymap-layouts"))
287 look_for = LAYOUTS;
288 else if (streq(argv[0], "list-x11-keymap-variants"))
289 look_for = VARIANTS;
290 else if (streq(argv[0], "list-x11-keymap-options"))
291 look_for = OPTIONS;
292 else
293 assert_not_reached();
294
295 for (;;) {
296 _cleanup_free_ char *line = NULL;
297 char *l, *w;
298
299 r = read_line(f, LONG_LINE_MAX, &line);
300 if (r < 0)
301 return log_error_errno(r, "Failed to read keyboard mapping list: %m");
302 if (r == 0)
303 break;
304
305 l = strstrip(line);
306
307 if (isempty(l))
308 continue;
309
310 if (l[0] == '!') {
311 if (startswith(l, "! model"))
312 state = MODELS;
313 else if (startswith(l, "! layout"))
314 state = LAYOUTS;
315 else if (startswith(l, "! variant"))
316 state = VARIANTS;
317 else if (startswith(l, "! option"))
318 state = OPTIONS;
319 else
320 state = NONE;
321
322 continue;
323 }
324
325 if (state != look_for)
326 continue;
327
328 w = l + strcspn(l, WHITESPACE);
329
330 if (argc > 1) {
331 char *e;
332
333 if (*w == 0)
334 continue;
335
336 *w = 0;
337 w++;
338 w += strspn(w, WHITESPACE);
339
340 e = strchr(w, ':');
341 if (!e)
342 continue;
343
344 *e = 0;
345
346 if (!streq(w, argv[1]))
347 continue;
348 } else
349 *w = 0;
350
351 r = strv_extend(&list, l);
352 if (r < 0)
353 return log_oom();
354 }
355
356 if (strv_isempty(list))
357 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
358 "Couldn't find any entries.");
359
360 strv_sort(list);
361 strv_uniq(list);
362
363 pager_open(arg_pager_flags);
364
365 strv_print(list);
366 return 0;
367 }
368
help(void)369 static int help(void) {
370 _cleanup_free_ char *link = NULL;
371 int r;
372
373 r = terminal_urlify_man("localectl", "1", &link);
374 if (r < 0)
375 return log_oom();
376
377 printf("%s [OPTIONS...] COMMAND ...\n\n"
378 "%sQuery or change system locale and keyboard settings.%s\n"
379 "\nCommands:\n"
380 " status Show current locale settings\n"
381 " set-locale LOCALE... Set system locale\n"
382 " list-locales Show known locales\n"
383 " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n"
384 " list-keymaps Show known virtual console keyboard mappings\n"
385 " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
386 " Set X11 and console keyboard mappings\n"
387 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
388 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
389 " list-x11-keymap-variants [LAYOUT]\n"
390 " Show known X11 keyboard mapping variants\n"
391 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
392 "\nOptions:\n"
393 " -h --help Show this help\n"
394 " --version Show package version\n"
395 " --no-pager Do not pipe output into a pager\n"
396 " --no-ask-password Do not prompt for password\n"
397 " -H --host=[USER@]HOST Operate on remote host\n"
398 " -M --machine=CONTAINER Operate on local container\n"
399 " --no-convert Don't convert keyboard mappings\n"
400 "\nSee the %s for details.\n",
401 program_invocation_short_name,
402 ansi_highlight(),
403 ansi_normal(),
404 link);
405
406 return 0;
407 }
408
verb_help(int argc,char ** argv,void * userdata)409 static int verb_help(int argc, char **argv, void *userdata) {
410 return help();
411 }
412
parse_argv(int argc,char * argv[])413 static int parse_argv(int argc, char *argv[]) {
414
415 enum {
416 ARG_VERSION = 0x100,
417 ARG_NO_PAGER,
418 ARG_NO_CONVERT,
419 ARG_NO_ASK_PASSWORD
420 };
421
422 static const struct option options[] = {
423 { "help", no_argument, NULL, 'h' },
424 { "version", no_argument, NULL, ARG_VERSION },
425 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
426 { "host", required_argument, NULL, 'H' },
427 { "machine", required_argument, NULL, 'M' },
428 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
429 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
430 {}
431 };
432
433 int c;
434
435 assert(argc >= 0);
436 assert(argv);
437
438 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
439
440 switch (c) {
441
442 case 'h':
443 return help();
444
445 case ARG_VERSION:
446 return version();
447
448 case ARG_NO_CONVERT:
449 arg_convert = false;
450 break;
451
452 case ARG_NO_PAGER:
453 arg_pager_flags |= PAGER_DISABLE;
454 break;
455
456 case ARG_NO_ASK_PASSWORD:
457 arg_ask_password = false;
458 break;
459
460 case 'H':
461 arg_transport = BUS_TRANSPORT_REMOTE;
462 arg_host = optarg;
463 break;
464
465 case 'M':
466 arg_transport = BUS_TRANSPORT_MACHINE;
467 arg_host = optarg;
468 break;
469
470 case '?':
471 return -EINVAL;
472
473 default:
474 assert_not_reached();
475 }
476
477 return 1;
478 }
479
localectl_main(sd_bus * bus,int argc,char * argv[])480 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
481
482 static const Verb verbs[] = {
483 { "status", VERB_ANY, 1, VERB_DEFAULT, show_status },
484 { "set-locale", 2, VERB_ANY, 0, set_locale },
485 { "list-locales", VERB_ANY, 1, 0, list_locales },
486 { "set-keymap", 2, 3, 0, set_vconsole_keymap },
487 { "list-keymaps", VERB_ANY, 1, 0, list_vconsole_keymaps },
488 { "set-x11-keymap", 2, 5, 0, set_x11_keymap },
489 { "list-x11-keymap-models", VERB_ANY, 1, 0, list_x11_keymaps },
490 { "list-x11-keymap-layouts", VERB_ANY, 1, 0, list_x11_keymaps },
491 { "list-x11-keymap-variants", VERB_ANY, 2, 0, list_x11_keymaps },
492 { "list-x11-keymap-options", VERB_ANY, 1, 0, list_x11_keymaps },
493 { "help", VERB_ANY, VERB_ANY, 0, verb_help }, /* Not documented, but supported since it is created. */
494 {}
495 };
496
497 return dispatch_verb(argc, argv, verbs, bus);
498 }
499
run(int argc,char * argv[])500 static int run(int argc, char *argv[]) {
501 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
502 int r;
503
504 setlocale(LC_ALL, "");
505 log_setup();
506
507 r = parse_argv(argc, argv);
508 if (r <= 0)
509 return r;
510
511 r = bus_connect_transport(arg_transport, arg_host, false, &bus);
512 if (r < 0)
513 return bus_log_connect_error(r, arg_transport);
514
515 return localectl_main(bus, argc, argv);
516 }
517
518 DEFINE_MAIN_FUNCTION(run);
519