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