chiark / gitweb /
localectl: print warning when there are options given on kernel cmdline
[elogind.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 = VARIABLE_LANG; 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 = VARIABLE_LANG; 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 }