]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
Merge pull request #1374 from olof/autoconf_gcrypt_dep
[thirdparty/systemd.git] / src / locale / localectl.c
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
7 Copyright 2013 Kay Sievers
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>
26 #include <getopt.h>
27 #include <string.h>
28 #include <ftw.h>
29
30 #include "sd-bus.h"
31 #include "bus-util.h"
32 #include "bus-error.h"
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"
39 #include "def.h"
40 #include "virt.h"
41 #include "fileio.h"
42 #include "locale-util.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 static void pager_open_if_enabled(void) {
51
52 if (arg_no_pager)
53 return;
54
55 pager_open(false);
56 }
57
58 static void polkit_agent_open_if_enabled(void) {
59
60 /* Open the polkit agent as a child process if necessary */
61 if (!arg_ask_password)
62 return;
63
64 if (arg_transport != BUS_TRANSPORT_LOCAL)
65 return;
66
67 polkit_agent_open();
68 }
69
70 typedef struct StatusInfo {
71 char **locale;
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;
78 } StatusInfo;
79
80 static 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
93 static void print_overridden_variables(void) {
94 int r;
95 char *variables[_VARIABLE_LC_MAX] = {};
96 LocaleVariable j;
97 bool print_warning = true;
98
99 if (detect_container() > 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) {
120 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
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"
128 " Command Line: %s=%s", locale_variable_to_string(j), variables[j]);
129
130 print_warning = false;
131 } else
132 log_warning(" %s=%s", locale_variable_to_string(j), variables[j]);
133 }
134 finish:
135 for (j = 0; j < _VARIABLE_LC_MAX; j++)
136 free(variables[j]);
137 }
138
139 static 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
165 static int show_status(sd_bus *bus, char **args, unsigned n) {
166 _cleanup_(status_info_clear) StatusInfo info = {};
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;
179
180 assert(bus);
181
182 r = bus_map_all_properties(bus,
183 "org.freedesktop.locale1",
184 "/org/freedesktop/locale1",
185 map,
186 &info);
187 if (r < 0)
188 return log_error_errno(r, "Could not get properties: %m");
189
190 print_overridden_variables();
191 print_status_info(&info);
192
193 return r;
194 }
195
196 static 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;
199 int r;
200
201 assert(bus);
202 assert(args);
203
204 polkit_agent_open_if_enabled();
205
206 r = sd_bus_message_new_method_call(
207 bus,
208 &m,
209 "org.freedesktop.locale1",
210 "/org/freedesktop/locale1",
211 "org.freedesktop.locale1",
212 "SetLocale");
213 if (r < 0)
214 return bus_log_create_error(r);
215
216 r = sd_bus_message_append_strv(m, args + 1);
217 if (r < 0)
218 return bus_log_create_error(r);
219
220 r = sd_bus_message_append(m, "b", arg_ask_password);
221 if (r < 0)
222 return bus_log_create_error(r);
223
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;
228 }
229
230 return 0;
231 }
232
233 static int list_locales(sd_bus *bus, char **args, unsigned n) {
234 _cleanup_strv_free_ char **l = NULL;
235 int r;
236
237 assert(args);
238
239 r = get_locales(&l);
240 if (r < 0)
241 return log_error_errno(r, "Failed to read list of locales: %m");
242
243 pager_open_if_enabled();
244 strv_print(l);
245
246 return 0;
247 }
248
249 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
250 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
251 const char *map, *toggle_map;
252 int r;
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] : "";
266
267 r = sd_bus_call_method(
268 bus,
269 "org.freedesktop.locale1",
270 "/org/freedesktop/locale1",
271 "org.freedesktop.locale1",
272 "SetVConsoleKeyboard",
273 &error,
274 NULL,
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;
280 }
281
282 static Set *keymaps = NULL;
283
284 static 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
300 p = strdup(basename(fpath));
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);
313 if (r < 0 && r != -EEXIST)
314 return log_error_errno(r, "Can't add keymap: %m");
315
316 return 0;
317 }
318
319 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
320 _cleanup_strv_free_ char **l = NULL;
321 const char *dir;
322
323 keymaps = set_new(&string_hash_ops);
324 if (!keymaps)
325 return log_oom();
326
327 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
328 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
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
352 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
353 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
354 const char *layout, *model, *variant, *options;
355 int r;
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] : "";
371
372 r = sd_bus_call_method(
373 bus,
374 "org.freedesktop.locale1",
375 "/org/freedesktop/locale1",
376 "org.freedesktop.locale1",
377 "SetX11Keyboard",
378 &error,
379 NULL,
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;
386 }
387
388 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
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");
407 if (!f)
408 return log_error_errno(errno, "Failed to open keyboard mapping list. %m");
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
489 static void help(void) {
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"
494 " --no-pager Do not pipe output into a pager\n"
495 " --no-ask-password Do not prompt for password\n"
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"
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"
503 " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n"
504 " list-keymaps Show known virtual console keyboard mappings\n"
505 " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
506 " Set X11 and console keyboard mappings\n"
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"
511 " list-x11-keymap-options Show known X11 keyboard mapping options\n"
512 , program_invocation_short_name);
513 }
514
515 static 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[] = {
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 {}
533 };
534
535 int c;
536
537 assert(argc >= 0);
538 assert(argv);
539
540 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
541
542 switch (c) {
543
544 case 'h':
545 help();
546 return 0;
547
548 case ARG_VERSION:
549 puts(PACKAGE_STRING);
550 puts(SYSTEMD_FEATURES);
551 return 0;
552
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
561 case ARG_NO_ASK_PASSWORD:
562 arg_ask_password = false;
563 break;
564
565 case 'H':
566 arg_transport = BUS_TRANSPORT_REMOTE;
567 arg_host = optarg;
568 break;
569
570 case 'M':
571 arg_transport = BUS_TRANSPORT_MACHINE;
572 arg_host = optarg;
573 break;
574
575 case '?':
576 return -EINVAL;
577
578 default:
579 assert_not_reached("Unhandled option");
580 }
581
582 return 1;
583 }
584
585 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
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;
595 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
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);
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
666 return verbs[i].dispatch(bus, argv + optind, left);
667 }
668
669 int main(int argc, char*argv[]) {
670 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
671 int r;
672
673 setlocale(LC_ALL, "");
674 log_parse_environment();
675 log_open();
676
677 r = parse_argv(argc, argv);
678 if (r <= 0)
679 goto finish;
680
681 r = bus_open_transport(arg_transport, arg_host, false, &bus);
682 if (r < 0) {
683 log_error_errno(r, "Failed to create bus connection: %m");
684 goto finish;
685 }
686
687 r = localectl_main(bus, argc, argv);
688
689 finish:
690 pager_close();
691
692 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
693 }