]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / locale / localectl.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
6 Copyright 2013 Kay Sievers
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <ftw.h>
23 #include <getopt.h>
24 #include <locale.h>
25 #include <stdbool.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "sd-bus.h"
30
31 #include "bus-error.h"
32 #include "bus-util.h"
33 #include "def.h"
34 #include "fd-util.h"
35 #include "fileio.h"
36 #include "locale-util.h"
37 #include "pager.h"
38 #include "set.h"
39 #include "spawn-polkit-agent.h"
40 #include "strv.h"
41 #include "util.h"
42 #include "virt.h"
43
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;
49
50 typedef struct StatusInfo {
51 char **locale;
52 char *vconsole_keymap;
53 char *vconsole_keymap_toggle;
54 char *x11_layout;
55 char *x11_model;
56 char *x11_variant;
57 char *x11_options;
58 } StatusInfo;
59
60 static void status_info_clear(StatusInfo *info) {
61 if (info) {
62 strv_free(info->locale);
63 free(info->vconsole_keymap);
64 free(info->vconsole_keymap_toggle);
65 free(info->x11_layout);
66 free(info->x11_model);
67 free(info->x11_variant);
68 free(info->x11_options);
69 zero(*info);
70 }
71 }
72
73 static void print_overridden_variables(void) {
74 int r;
75 char *variables[_VARIABLE_LC_MAX] = {};
76 LocaleVariable j;
77 bool print_warning = true;
78
79 if (detect_container() > 0 || arg_host)
80 return;
81
82 r = parse_env_file("/proc/cmdline", WHITESPACE,
83 "locale.LANG", &variables[VARIABLE_LANG],
84 "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE],
85 "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE],
86 "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC],
87 "locale.LC_TIME", &variables[VARIABLE_LC_TIME],
88 "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE],
89 "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY],
90 "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES],
91 "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER],
92 "locale.LC_NAME", &variables[VARIABLE_LC_NAME],
93 "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS],
94 "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE],
95 "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT],
96 "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION],
97 NULL);
98
99 if (r < 0 && r != -ENOENT) {
100 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
101 goto finish;
102 }
103
104 for (j = 0; j < _VARIABLE_LC_MAX; j++)
105 if (variables[j]) {
106 if (print_warning) {
107 log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n"
108 " Command Line: %s=%s", locale_variable_to_string(j), variables[j]);
109
110 print_warning = false;
111 } else
112 log_warning(" %s=%s", locale_variable_to_string(j), variables[j]);
113 }
114 finish:
115 for (j = 0; j < _VARIABLE_LC_MAX; j++)
116 free(variables[j]);
117 }
118
119 static void print_status_info(StatusInfo *i) {
120 assert(i);
121
122 if (strv_isempty(i->locale))
123 puts(" System Locale: n/a");
124 else {
125 char **j;
126
127 printf(" System Locale: %s\n", i->locale[0]);
128 STRV_FOREACH(j, i->locale + 1)
129 printf(" %s\n", *j);
130 }
131
132 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
133 if (!isempty(i->vconsole_keymap_toggle))
134 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
135
136 printf(" X11 Layout: %s\n", strna(i->x11_layout));
137 if (!isempty(i->x11_model))
138 printf(" X11 Model: %s\n", i->x11_model);
139 if (!isempty(i->x11_variant))
140 printf(" X11 Variant: %s\n", i->x11_variant);
141 if (!isempty(i->x11_options))
142 printf(" X11 Options: %s\n", i->x11_options);
143 }
144
145 static int show_status(sd_bus *bus, char **args, unsigned n) {
146 _cleanup_(status_info_clear) StatusInfo info = {};
147 static const struct bus_properties_map map[] = {
148 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
149 { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) },
150 { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
151 { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) },
152 { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) },
153 { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) },
154 { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) },
155 { "Locale", "as", NULL, offsetof(StatusInfo, locale) },
156 {}
157 };
158
159 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
160 int r;
161
162 assert(bus);
163
164 r = bus_map_all_properties(bus,
165 "org.freedesktop.locale1",
166 "/org/freedesktop/locale1",
167 map,
168 &error,
169 &info);
170 if (r < 0)
171 return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
172
173 print_overridden_variables();
174 print_status_info(&info);
175
176 return r;
177 }
178
179 static int set_locale(sd_bus *bus, char **args, unsigned n) {
180 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
181 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
182 int r;
183
184 assert(bus);
185 assert(args);
186
187 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
188
189 r = sd_bus_message_new_method_call(
190 bus,
191 &m,
192 "org.freedesktop.locale1",
193 "/org/freedesktop/locale1",
194 "org.freedesktop.locale1",
195 "SetLocale");
196 if (r < 0)
197 return bus_log_create_error(r);
198
199 r = sd_bus_message_append_strv(m, args + 1);
200 if (r < 0)
201 return bus_log_create_error(r);
202
203 r = sd_bus_message_append(m, "b", arg_ask_password);
204 if (r < 0)
205 return bus_log_create_error(r);
206
207 r = sd_bus_call(bus, m, 0, &error, NULL);
208 if (r < 0) {
209 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
210 return r;
211 }
212
213 return 0;
214 }
215
216 static int list_locales(sd_bus *bus, char **args, unsigned n) {
217 _cleanup_strv_free_ char **l = NULL;
218 int r;
219
220 assert(args);
221
222 r = get_locales(&l);
223 if (r < 0)
224 return log_error_errno(r, "Failed to read list of locales: %m");
225
226 pager_open(arg_no_pager, false);
227 strv_print(l);
228
229 return 0;
230 }
231
232 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
233 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
234 const char *map, *toggle_map;
235 int r;
236
237 assert(bus);
238 assert(args);
239
240 if (n > 3) {
241 log_error("Too many arguments.");
242 return -EINVAL;
243 }
244
245 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
246
247 map = args[1];
248 toggle_map = n > 2 ? args[2] : "";
249
250 r = sd_bus_call_method(
251 bus,
252 "org.freedesktop.locale1",
253 "/org/freedesktop/locale1",
254 "org.freedesktop.locale1",
255 "SetVConsoleKeyboard",
256 &error,
257 NULL,
258 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
259 if (r < 0)
260 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
261
262 return r;
263 }
264
265 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
266 _cleanup_strv_free_ char **l = NULL;
267 int r;
268
269 assert(args);
270
271 r = get_keymaps(&l);
272 if (r < 0)
273 return log_error_errno(r, "Failed to read list of keymaps: %m");
274
275 pager_open(arg_no_pager, false);
276
277 strv_print(l);
278
279 return 0;
280 }
281
282 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
283 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
284 const char *layout, *model, *variant, *options;
285 int r;
286
287 assert(bus);
288 assert(args);
289
290 if (n > 5) {
291 log_error("Too many arguments.");
292 return -EINVAL;
293 }
294
295 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
296
297 layout = args[1];
298 model = n > 2 ? args[2] : "";
299 variant = n > 3 ? args[3] : "";
300 options = n > 4 ? args[4] : "";
301
302 r = sd_bus_call_method(
303 bus,
304 "org.freedesktop.locale1",
305 "/org/freedesktop/locale1",
306 "org.freedesktop.locale1",
307 "SetX11Keyboard",
308 &error,
309 NULL,
310 "ssssbb", layout, model, variant, options,
311 arg_convert, arg_ask_password);
312 if (r < 0)
313 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
314
315 return r;
316 }
317
318 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
319 _cleanup_fclose_ FILE *f = NULL;
320 _cleanup_strv_free_ char **list = NULL;
321 char line[LINE_MAX];
322 enum {
323 NONE,
324 MODELS,
325 LAYOUTS,
326 VARIANTS,
327 OPTIONS
328 } state = NONE, look_for;
329 int r;
330
331 if (n > 2) {
332 log_error("Too many arguments.");
333 return -EINVAL;
334 }
335
336 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
337 if (!f)
338 return log_error_errno(errno, "Failed to open keyboard mapping list. %m");
339
340 if (streq(args[0], "list-x11-keymap-models"))
341 look_for = MODELS;
342 else if (streq(args[0], "list-x11-keymap-layouts"))
343 look_for = LAYOUTS;
344 else if (streq(args[0], "list-x11-keymap-variants"))
345 look_for = VARIANTS;
346 else if (streq(args[0], "list-x11-keymap-options"))
347 look_for = OPTIONS;
348 else
349 assert_not_reached("Wrong parameter");
350
351 FOREACH_LINE(line, f, break) {
352 char *l, *w;
353
354 l = strstrip(line);
355
356 if (isempty(l))
357 continue;
358
359 if (l[0] == '!') {
360 if (startswith(l, "! model"))
361 state = MODELS;
362 else if (startswith(l, "! layout"))
363 state = LAYOUTS;
364 else if (startswith(l, "! variant"))
365 state = VARIANTS;
366 else if (startswith(l, "! option"))
367 state = OPTIONS;
368 else
369 state = NONE;
370
371 continue;
372 }
373
374 if (state != look_for)
375 continue;
376
377 w = l + strcspn(l, WHITESPACE);
378
379 if (n > 1) {
380 char *e;
381
382 if (*w == 0)
383 continue;
384
385 *w = 0;
386 w++;
387 w += strspn(w, WHITESPACE);
388
389 e = strchr(w, ':');
390 if (!e)
391 continue;
392
393 *e = 0;
394
395 if (!streq(w, args[1]))
396 continue;
397 } else
398 *w = 0;
399
400 r = strv_extend(&list, l);
401 if (r < 0)
402 return log_oom();
403 }
404
405 if (strv_isempty(list)) {
406 log_error("Couldn't find any entries.");
407 return -ENOENT;
408 }
409
410 strv_sort(list);
411 strv_uniq(list);
412
413 pager_open(arg_no_pager, false);
414
415 strv_print(list);
416 return 0;
417 }
418
419 static void help(void) {
420 printf("%s [OPTIONS...] COMMAND ...\n\n"
421 "Query or change system locale and keyboard settings.\n\n"
422 " -h --help Show this help\n"
423 " --version Show package version\n"
424 " --no-pager Do not pipe output into a pager\n"
425 " --no-ask-password Do not prompt for password\n"
426 " -H --host=[USER@]HOST Operate on remote host\n"
427 " -M --machine=CONTAINER Operate on local container\n"
428 " --no-convert Don't convert keyboard mappings\n\n"
429 "Commands:\n"
430 " status Show current locale settings\n"
431 " set-locale LOCALE... Set system locale\n"
432 " list-locales Show known locales\n"
433 " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n"
434 " list-keymaps Show known virtual console keyboard mappings\n"
435 " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
436 " Set X11 and console keyboard mappings\n"
437 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
438 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
439 " list-x11-keymap-variants [LAYOUT]\n"
440 " Show known X11 keyboard mapping variants\n"
441 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
442 , program_invocation_short_name);
443 }
444
445 static int parse_argv(int argc, char *argv[]) {
446
447 enum {
448 ARG_VERSION = 0x100,
449 ARG_NO_PAGER,
450 ARG_NO_CONVERT,
451 ARG_NO_ASK_PASSWORD
452 };
453
454 static const struct option options[] = {
455 { "help", no_argument, NULL, 'h' },
456 { "version", no_argument, NULL, ARG_VERSION },
457 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
458 { "host", required_argument, NULL, 'H' },
459 { "machine", required_argument, NULL, 'M' },
460 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
461 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
462 {}
463 };
464
465 int c;
466
467 assert(argc >= 0);
468 assert(argv);
469
470 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
471
472 switch (c) {
473
474 case 'h':
475 help();
476 return 0;
477
478 case ARG_VERSION:
479 return version();
480
481 case ARG_NO_CONVERT:
482 arg_convert = false;
483 break;
484
485 case ARG_NO_PAGER:
486 arg_no_pager = true;
487 break;
488
489 case ARG_NO_ASK_PASSWORD:
490 arg_ask_password = false;
491 break;
492
493 case 'H':
494 arg_transport = BUS_TRANSPORT_REMOTE;
495 arg_host = optarg;
496 break;
497
498 case 'M':
499 arg_transport = BUS_TRANSPORT_MACHINE;
500 arg_host = optarg;
501 break;
502
503 case '?':
504 return -EINVAL;
505
506 default:
507 assert_not_reached("Unhandled option");
508 }
509
510 return 1;
511 }
512
513 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
514
515 static const struct {
516 const char* verb;
517 const enum {
518 MORE,
519 LESS,
520 EQUAL
521 } argc_cmp;
522 const int argc;
523 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
524 } verbs[] = {
525 { "status", LESS, 1, show_status },
526 { "set-locale", MORE, 2, set_locale },
527 { "list-locales", EQUAL, 1, list_locales },
528 { "set-keymap", MORE, 2, set_vconsole_keymap },
529 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
530 { "set-x11-keymap", MORE, 2, set_x11_keymap },
531 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
532 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
533 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
534 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
535 };
536
537 int left;
538 unsigned i;
539
540 assert(argc >= 0);
541 assert(argv);
542
543 left = argc - optind;
544
545 if (left <= 0)
546 /* Special rule: no arguments means "status" */
547 i = 0;
548 else {
549 if (streq(argv[optind], "help")) {
550 help();
551 return 0;
552 }
553
554 for (i = 0; i < ELEMENTSOF(verbs); i++)
555 if (streq(argv[optind], verbs[i].verb))
556 break;
557
558 if (i >= ELEMENTSOF(verbs)) {
559 log_error("Unknown operation %s", argv[optind]);
560 return -EINVAL;
561 }
562 }
563
564 switch (verbs[i].argc_cmp) {
565
566 case EQUAL:
567 if (left != verbs[i].argc) {
568 log_error("Invalid number of arguments.");
569 return -EINVAL;
570 }
571
572 break;
573
574 case MORE:
575 if (left < verbs[i].argc) {
576 log_error("Too few arguments.");
577 return -EINVAL;
578 }
579
580 break;
581
582 case LESS:
583 if (left > verbs[i].argc) {
584 log_error("Too many arguments.");
585 return -EINVAL;
586 }
587
588 break;
589
590 default:
591 assert_not_reached("Unknown comparison operator.");
592 }
593
594 return verbs[i].dispatch(bus, argv + optind, left);
595 }
596
597 int main(int argc, char*argv[]) {
598 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
599 int r;
600
601 setlocale(LC_ALL, "");
602 log_parse_environment();
603 log_open();
604
605 r = parse_argv(argc, argv);
606 if (r <= 0)
607 goto finish;
608
609 r = bus_connect_transport(arg_transport, arg_host, false, &bus);
610 if (r < 0) {
611 log_error_errno(r, "Failed to create bus connection: %m");
612 goto finish;
613 }
614
615 r = localectl_main(bus, argc, argv);
616
617 finish:
618 pager_close();
619
620 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
621 }