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