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