]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localectl.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / locale / localectl.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
2087a7af
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
4d7859d1 6 Copyright 2013 Kay Sievers
2087a7af
LP
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
3f6fd1ba
LP
22#include <ftw.h>
23#include <getopt.h>
a9cdc94f 24#include <locale.h>
2087a7af 25#include <stdbool.h>
3f6fd1ba 26#include <stdlib.h>
2087a7af 27#include <string.h>
2087a7af 28
4d7859d1 29#include "sd-bus.h"
3f6fd1ba 30
4d7859d1 31#include "bus-error.h"
3f6fd1ba 32#include "bus-util.h"
0732ef7a 33#include "def.h"
3ffd4af2 34#include "fd-util.h"
a3428668 35#include "fileio.h"
75683450 36#include "locale-util.h"
3f6fd1ba
LP
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"
2087a7af
LP
43
44static bool arg_no_pager = false;
2087a7af 45static bool arg_ask_password = true;
4d7859d1 46static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
7085053a 47static char *arg_host = NULL;
2087a7af
LP
48static bool arg_convert = true;
49
2087a7af
LP
50typedef struct StatusInfo {
51 char **locale;
e7e55dbd
DH
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;
2087a7af
LP
58} StatusInfo;
59
e7e55dbd
DH
60static 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
ff9b60f3 73static void print_overridden_variables(void) {
a3428668
MS
74 int r;
75 char *variables[_VARIABLE_LC_MAX] = {};
76 LocaleVariable j;
77 bool print_warning = true;
78
75f86906 79 if (detect_container() > 0 || arg_host)
a3428668
MS
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) {
da927ba9 100 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
a3428668
MS
101 goto finish;
102 }
103
63229aa1 104 for (j = 0; j < _VARIABLE_LC_MAX; j++)
a3428668
MS
105 if (variables[j]) {
106 if (print_warning) {
b344bcbb 107 log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n"
c86b2d8f 108 " Command Line: %s=%s", locale_variable_to_string(j), variables[j]);
a3428668
MS
109
110 print_warning = false;
b344bcbb 111 } else
c86b2d8f 112 log_warning(" %s=%s", locale_variable_to_string(j), variables[j]);
a3428668
MS
113 }
114 finish:
63229aa1 115 for (j = 0; j < _VARIABLE_LC_MAX; j++)
a3428668
MS
116 free(variables[j]);
117}
118
2087a7af
LP
119static void print_status_info(StatusInfo *i) {
120 assert(i);
121
122 if (strv_isempty(i->locale))
a75db59c 123 puts(" System Locale: n/a");
2087a7af
LP
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
4d7859d1 145static int show_status(sd_bus *bus, char **args, unsigned n) {
e7e55dbd 146 _cleanup_(status_info_clear) StatusInfo info = {};
9f6eb1cd
KS
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) },
ffc06c35
KS
156 {}
157 };
f9e0eefc
LP
158
159 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
ffc06c35 160 int r;
2087a7af 161
ffc06c35 162 assert(bus);
2087a7af 163
ffc06c35
KS
164 r = bus_map_all_properties(bus,
165 "org.freedesktop.locale1",
166 "/org/freedesktop/locale1",
9f6eb1cd 167 map,
f9e0eefc 168 &error,
9f6eb1cd 169 &info);
e7e55dbd 170 if (r < 0)
f9e0eefc 171 return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
2087a7af 172
ff9b60f3 173 print_overridden_variables();
2087a7af 174 print_status_info(&info);
4d7859d1 175
4d7859d1 176 return r;
2087a7af
LP
177}
178
4d7859d1 179static int set_locale(sd_bus *bus, char **args, unsigned n) {
4afd3348
LP
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;
2087a7af
LP
182 int r;
183
184 assert(bus);
185 assert(args);
186
8a4b13c5 187 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
2087a7af 188
151b9b96
LP
189 r = sd_bus_message_new_method_call(
190 bus,
191 &m,
2087a7af
LP
192 "org.freedesktop.locale1",
193 "/org/freedesktop/locale1",
194 "org.freedesktop.locale1",
151b9b96 195 "SetLocale");
4d7859d1 196 if (r < 0)
94676f3e 197 return bus_log_create_error(r);
2087a7af 198
4d7859d1
KS
199 r = sd_bus_message_append_strv(m, args + 1);
200 if (r < 0)
94676f3e 201 return bus_log_create_error(r);
2087a7af 202
4d7859d1 203 r = sd_bus_message_append(m, "b", arg_ask_password);
2087a7af 204 if (r < 0)
94676f3e 205 return bus_log_create_error(r);
2087a7af 206
c49b30a2 207 r = sd_bus_call(bus, m, 0, &error, NULL);
4d7859d1 208 if (r < 0) {
e1636421 209 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
4d7859d1 210 return r;
2087a7af
LP
211 }
212
4d7859d1 213 return 0;
2087a7af
LP
214}
215
4d7859d1 216static int list_locales(sd_bus *bus, char **args, unsigned n) {
17d33cec 217 _cleanup_strv_free_ char **l = NULL;
17d33cec
GC
218 int r;
219
75683450 220 assert(args);
17d33cec 221
75683450 222 r = get_locales(&l);
f647962d
MS
223 if (r < 0)
224 return log_error_errno(r, "Failed to read list of locales: %m");
2087a7af 225
ea4b98e6 226 pager_open(arg_no_pager, false);
7c2d8094 227 strv_print(l);
2087a7af 228
bac3c8ee 229 return 0;
2087a7af
LP
230}
231
4d7859d1 232static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
4afd3348 233 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2087a7af 234 const char *map, *toggle_map;
e1636421 235 int r;
2087a7af
LP
236
237 assert(bus);
238 assert(args);
239
240 if (n > 3) {
241 log_error("Too many arguments.");
242 return -EINVAL;
243 }
244
8a4b13c5 245 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
2087a7af
LP
246
247 map = args[1];
248 toggle_map = n > 2 ? args[2] : "";
2087a7af 249
e1636421
LP
250 r = sd_bus_call_method(
251 bus,
2087a7af
LP
252 "org.freedesktop.locale1",
253 "/org/freedesktop/locale1",
254 "org.freedesktop.locale1",
255 "SetVConsoleKeyboard",
4d7859d1 256 &error,
2087a7af 257 NULL,
4d7859d1 258 "ssbb", map, toggle_map, arg_convert, arg_ask_password);
e1636421
LP
259 if (r < 0)
260 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
261
262 return r;
2087a7af
LP
263}
264
4d7859d1 265static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
ed457f13
TB
266 _cleanup_strv_free_ char **l = NULL;
267 int r;
2087a7af 268
ed457f13 269 assert(args);
2087a7af 270
ed457f13
TB
271 r = get_keymaps(&l);
272 if (r < 0)
273 return log_error_errno(r, "Failed to read list of keymaps: %m");
2087a7af 274
ea4b98e6 275 pager_open(arg_no_pager, false);
2087a7af 276
7c2d8094 277 strv_print(l);
2087a7af
LP
278
279 return 0;
280}
281
4d7859d1 282static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
4afd3348 283 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2087a7af 284 const char *layout, *model, *variant, *options;
e1636421 285 int r;
2087a7af
LP
286
287 assert(bus);
288 assert(args);
289
290 if (n > 5) {
291 log_error("Too many arguments.");
292 return -EINVAL;
293 }
294
8a4b13c5 295 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
2087a7af
LP
296
297 layout = args[1];
298 model = n > 2 ? args[2] : "";
299 variant = n > 3 ? args[3] : "";
6b2b6f30 300 options = n > 4 ? args[4] : "";
2087a7af 301
e1636421
LP
302 r = sd_bus_call_method(
303 bus,
2087a7af
LP
304 "org.freedesktop.locale1",
305 "/org/freedesktop/locale1",
306 "org.freedesktop.locale1",
307 "SetX11Keyboard",
4d7859d1 308 &error,
2087a7af 309 NULL,
4d7859d1
KS
310 "ssssbb", layout, model, variant, options,
311 arg_convert, arg_ask_password);
e1636421
LP
312 if (r < 0)
313 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
314
315 return r;
2087a7af
LP
316}
317
4d7859d1 318static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
50cfc579 319 _cleanup_fclose_ FILE *f = NULL;
7fd1b19b 320 _cleanup_strv_free_ char **list = NULL;
50cfc579
LP
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
c62e11ce 336 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
4a62c710
MS
337 if (!f)
338 return log_error_errno(errno, "Failed to open keyboard mapping list. %m");
50cfc579
LP
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
7d6884b6
TA
400 r = strv_extend(&list, l);
401 if (r < 0)
402 return log_oom();
50cfc579
LP
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
ea4b98e6 413 pager_open(arg_no_pager, false);
50cfc579
LP
414
415 strv_print(list);
416 return 0;
417}
418
601185b4 419static void help(void) {
2087a7af 420 printf("%s [OPTIONS...] COMMAND ...\n\n"
82c1d8f4 421 "Query or change system locale and keyboard settings.\n\n"
50cfc579
LP
422 " -h --help Show this help\n"
423 " --version Show package version\n"
50cfc579
LP
424 " --no-pager Do not pipe output into a pager\n"
425 " --no-ask-password Do not prompt for password\n"
4d7859d1 426 " -H --host=[USER@]HOST Operate on remote host\n"
a86a47ce
LP
427 " -M --machine=CONTAINER Operate on local container\n"
428 " --no-convert Don't convert keyboard mappings\n\n"
2087a7af 429 "Commands:\n"
50cfc579
LP
430 " status Show current locale settings\n"
431 " set-locale LOCALE... Set system locale\n"
432 " list-locales Show known locales\n"
2ebcf936 433 " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n"
50cfc579 434 " list-keymaps Show known virtual console keyboard mappings\n"
31cf921a 435 " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
2ebcf936 436 " Set X11 and console keyboard mappings\n"
50cfc579
LP
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"
601185b4
ZJS
441 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
442 , program_invocation_short_name);
2087a7af
LP
443}
444
445static 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[] = {
4d7859d1
KS
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 },
eb9da376 462 {}
2087a7af
LP
463 };
464
465 int c;
466
467 assert(argc >= 0);
468 assert(argv);
469
601185b4 470 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
2087a7af
LP
471
472 switch (c) {
473
474 case 'h':
601185b4
ZJS
475 help();
476 return 0;
2087a7af
LP
477
478 case ARG_VERSION:
3f6fd1ba 479 return version();
2087a7af 480
2087a7af
LP
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
546158bc
JJ
489 case ARG_NO_ASK_PASSWORD:
490 arg_ask_password = false;
491 break;
492
4d7859d1
KS
493 case 'H':
494 arg_transport = BUS_TRANSPORT_REMOTE;
495 arg_host = optarg;
496 break;
497
498 case 'M':
de33fc62 499 arg_transport = BUS_TRANSPORT_MACHINE;
4d7859d1
KS
500 arg_host = optarg;
501 break;
502
2087a7af
LP
503 case '?':
504 return -EINVAL;
505
506 default:
eb9da376 507 assert_not_reached("Unhandled option");
2087a7af 508 }
2087a7af
LP
509
510 return 1;
511}
512
4d7859d1 513static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
2087a7af
LP
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;
4d7859d1 523 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
2087a7af 524 } verbs[] = {
50cfc579
LP
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 },
2087a7af
LP
535 };
536
537 int left;
538 unsigned i;
539
540 assert(argc >= 0);
541 assert(argv);
2087a7af
LP
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
2087a7af
LP
594 return verbs[i].dispatch(bus, argv + optind, left);
595}
596
4d7859d1 597int main(int argc, char*argv[]) {
0b3c84eb 598 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
84f6181c 599 int r;
2087a7af 600
a9cdc94f 601 setlocale(LC_ALL, "");
2087a7af
LP
602 log_parse_environment();
603 log_open();
604
605 r = parse_argv(argc, argv);
84f6181c 606 if (r <= 0)
2087a7af 607 goto finish;
2087a7af 608
266f3e26 609 r = bus_connect_transport(arg_transport, arg_host, false, &bus);
4d7859d1 610 if (r < 0) {
da927ba9 611 log_error_errno(r, "Failed to create bus connection: %m");
4d7859d1 612 goto finish;
2087a7af
LP
613 }
614
4d7859d1 615 r = localectl_main(bus, argc, argv);
2087a7af 616
4d7859d1 617finish:
2087a7af
LP
618 pager_close();
619
84f6181c 620 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
2087a7af 621}