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