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