chiark / gitweb /
sd-bus: rename sd_bus_open_system_container() to sd_bus_open_system_machine()
[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_errno(r, "Failed to read /proc/cmdline: %m");
114                 goto finish;
115         }
116
117         for (j = 0; j < _VARIABLE_LC_MAX; j++)
118                 if (variables[j]) {
119                         if (print_warning) {
120                                 log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n"
121                                             "  Command Line: %s=%s", locale_variable_to_string(j), variables[j]);
122
123                                 print_warning = false;
124                         } else
125                                 log_warning("                %s=%s", locale_variable_to_string(j), variables[j]);
126                 }
127  finish:
128         for (j = 0; j < _VARIABLE_LC_MAX; j++)
129                 free(variables[j]);
130 }
131
132 static void print_status_info(StatusInfo *i) {
133         assert(i);
134
135         if (strv_isempty(i->locale))
136                 puts("   System Locale: n/a\n");
137         else {
138                 char **j;
139
140                 printf("   System Locale: %s\n", i->locale[0]);
141                 STRV_FOREACH(j, i->locale + 1)
142                         printf("                  %s\n", *j);
143         }
144
145         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
146         if (!isempty(i->vconsole_keymap_toggle))
147                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
148
149         printf("      X11 Layout: %s\n", strna(i->x11_layout));
150         if (!isempty(i->x11_model))
151                 printf("       X11 Model: %s\n", i->x11_model);
152         if (!isempty(i->x11_variant))
153                 printf("     X11 Variant: %s\n", i->x11_variant);
154         if (!isempty(i->x11_options))
155                 printf("     X11 Options: %s\n", i->x11_options);
156 }
157
158 static int show_status(sd_bus *bus, char **args, unsigned n) {
159         StatusInfo info = {};
160         static const struct bus_properties_map map[]  = {
161                 { "VConsoleKeymap",       "s",  NULL, offsetof(StatusInfo, vconsole_keymap) },
162                 { "VConsoleKeymap",       "s",  NULL, offsetof(StatusInfo, vconsole_keymap) },
163                 { "VConsoleKeymapToggle", "s",  NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
164                 { "X11Layout",            "s",  NULL, offsetof(StatusInfo, x11_layout) },
165                 { "X11Model",             "s",  NULL, offsetof(StatusInfo, x11_model) },
166                 { "X11Variant",           "s",  NULL, offsetof(StatusInfo, x11_variant) },
167                 { "X11Options",           "s",  NULL, offsetof(StatusInfo, x11_options) },
168                 { "Locale",               "as", NULL, offsetof(StatusInfo, locale) },
169                 {}
170         };
171         int r;
172
173         assert(bus);
174
175         r = bus_map_all_properties(bus,
176                                    "org.freedesktop.locale1",
177                                    "/org/freedesktop/locale1",
178                                    map,
179                                    &info);
180         if (r < 0) {
181                 log_error_errno(r, "Could not get properties: %m");
182                 goto fail;
183         }
184
185         print_overriden_variables();
186         print_status_info(&info);
187
188 fail:
189         strv_free(info.locale);
190         return r;
191 }
192
193 static int set_locale(sd_bus *bus, char **args, unsigned n) {
194         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
195         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
196         int r;
197
198         assert(bus);
199         assert(args);
200
201         polkit_agent_open_if_enabled();
202
203         r = sd_bus_message_new_method_call(
204                         bus,
205                         &m,
206                         "org.freedesktop.locale1",
207                         "/org/freedesktop/locale1",
208                         "org.freedesktop.locale1",
209                         "SetLocale");
210         if (r < 0)
211                 return bus_log_create_error(r);
212
213         r = sd_bus_message_append_strv(m, args + 1);
214         if (r < 0)
215                 return bus_log_create_error(r);
216
217         r = sd_bus_message_append(m, "b", arg_ask_password);
218         if (r < 0)
219                 return bus_log_create_error(r);
220
221         r = sd_bus_call(bus, m, 0, &error, NULL);
222         if (r < 0) {
223                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
224                 return r;
225         }
226
227         return 0;
228 }
229
230 static int list_locales(sd_bus *bus, char **args, unsigned n) {
231         _cleanup_strv_free_ char **l = NULL;
232         int r;
233
234         assert(args);
235
236         r = get_locales(&l);
237         if (r < 0)
238                 return log_error_errno(r, "Failed to read list of locales: %m");
239
240         pager_open_if_enabled();
241         strv_print(l);
242
243         return 0;
244 }
245
246 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
247         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
248         const char *map, *toggle_map;
249         int r;
250
251         assert(bus);
252         assert(args);
253
254         if (n > 3) {
255                 log_error("Too many arguments.");
256                 return -EINVAL;
257         }
258
259         polkit_agent_open_if_enabled();
260
261         map = args[1];
262         toggle_map = n > 2 ? args[2] : "";
263
264         r = sd_bus_call_method(
265                         bus,
266                         "org.freedesktop.locale1",
267                         "/org/freedesktop/locale1",
268                         "org.freedesktop.locale1",
269                         "SetVConsoleKeyboard",
270                         &error,
271                         NULL,
272                         "ssbb", map, toggle_map, arg_convert, arg_ask_password);
273         if (r < 0)
274                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
275
276         return r;
277 }
278
279 static Set *keymaps = NULL;
280
281 static int nftw_cb(
282                 const char *fpath,
283                 const struct stat *sb,
284                 int tflag,
285                 struct FTW *ftwbuf) {
286
287         char *p, *e;
288         int r;
289
290         if (tflag != FTW_F)
291                 return 0;
292
293         if (!endswith(fpath, ".map") &&
294             !endswith(fpath, ".map.gz"))
295                 return 0;
296
297         p = strdup(basename(fpath));
298         if (!p)
299                 return log_oom();
300
301         e = endswith(p, ".map");
302         if (e)
303                 *e = 0;
304
305         e = endswith(p, ".map.gz");
306         if (e)
307                 *e = 0;
308
309         r = set_consume(keymaps, p);
310         if (r < 0 && r != -EEXIST)
311                 return log_error_errno(r, "Can't add keymap: %m");
312
313         return 0;
314 }
315
316 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
317         _cleanup_strv_free_ char **l = NULL;
318         const char *dir;
319
320         keymaps = set_new(&string_hash_ops);
321         if (!keymaps)
322                 return log_oom();
323
324         NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
325                 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
326
327         l = set_get_strv(keymaps);
328         if (!l) {
329                 set_free_free(keymaps);
330                 return log_oom();
331         }
332
333         set_free(keymaps);
334
335         if (strv_isempty(l)) {
336                 log_error("Couldn't find any console keymaps.");
337                 return -ENOENT;
338         }
339
340         strv_sort(l);
341
342         pager_open_if_enabled();
343
344         strv_print(l);
345
346         return 0;
347 }
348
349 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
350         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
351         const char *layout, *model, *variant, *options;
352         int r;
353
354         assert(bus);
355         assert(args);
356
357         if (n > 5) {
358                 log_error("Too many arguments.");
359                 return -EINVAL;
360         }
361
362         polkit_agent_open_if_enabled();
363
364         layout = args[1];
365         model = n > 2 ? args[2] : "";
366         variant = n > 3 ? args[3] : "";
367         options = n > 4 ? args[4] : "";
368
369         r = sd_bus_call_method(
370                         bus,
371                         "org.freedesktop.locale1",
372                         "/org/freedesktop/locale1",
373                         "org.freedesktop.locale1",
374                         "SetX11Keyboard",
375                         &error,
376                         NULL,
377                         "ssssbb", layout, model, variant, options,
378                                   arg_convert, arg_ask_password);
379         if (r < 0)
380                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
381
382         return r;
383 }
384
385 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
386         _cleanup_fclose_ FILE *f = NULL;
387         _cleanup_strv_free_ char **list = NULL;
388         char line[LINE_MAX];
389         enum {
390                 NONE,
391                 MODELS,
392                 LAYOUTS,
393                 VARIANTS,
394                 OPTIONS
395         } state = NONE, look_for;
396         int r;
397
398         if (n > 2) {
399                 log_error("Too many arguments.");
400                 return -EINVAL;
401         }
402
403         f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
404         if (!f)
405                 return log_error_errno(errno, "Failed to open keyboard mapping list. %m");
406
407         if (streq(args[0], "list-x11-keymap-models"))
408                 look_for = MODELS;
409         else if (streq(args[0], "list-x11-keymap-layouts"))
410                 look_for = LAYOUTS;
411         else if (streq(args[0], "list-x11-keymap-variants"))
412                 look_for = VARIANTS;
413         else if (streq(args[0], "list-x11-keymap-options"))
414                 look_for = OPTIONS;
415         else
416                 assert_not_reached("Wrong parameter");
417
418         FOREACH_LINE(line, f, break) {
419                 char *l, *w;
420
421                 l = strstrip(line);
422
423                 if (isempty(l))
424                         continue;
425
426                 if (l[0] == '!') {
427                         if (startswith(l, "! model"))
428                                 state = MODELS;
429                         else if (startswith(l, "! layout"))
430                                 state = LAYOUTS;
431                         else if (startswith(l, "! variant"))
432                                 state = VARIANTS;
433                         else if (startswith(l, "! option"))
434                                 state = OPTIONS;
435                         else
436                                 state = NONE;
437
438                         continue;
439                 }
440
441                 if (state != look_for)
442                         continue;
443
444                 w = l + strcspn(l, WHITESPACE);
445
446                 if (n > 1) {
447                         char *e;
448
449                         if (*w == 0)
450                                 continue;
451
452                         *w = 0;
453                         w++;
454                         w += strspn(w, WHITESPACE);
455
456                         e = strchr(w, ':');
457                         if (!e)
458                                 continue;
459
460                         *e = 0;
461
462                         if (!streq(w, args[1]))
463                                 continue;
464                 } else
465                         *w = 0;
466
467                  r = strv_extend(&list, l);
468                  if (r < 0)
469                          return log_oom();
470         }
471
472         if (strv_isempty(list)) {
473                 log_error("Couldn't find any entries.");
474                 return -ENOENT;
475         }
476
477         strv_sort(list);
478         strv_uniq(list);
479
480         pager_open_if_enabled();
481
482         strv_print(list);
483         return 0;
484 }
485
486 static void help(void) {
487         printf("%s [OPTIONS...] COMMAND ...\n\n"
488                "Query or change system locale and keyboard settings.\n\n"
489                "  -h --help                Show this help\n"
490                "     --version             Show package version\n"
491                "     --no-pager            Do not pipe output into a pager\n"
492                "     --no-ask-password     Do not prompt for password\n"
493                "  -H --host=[USER@]HOST    Operate on remote host\n"
494                "  -M --machine=CONTAINER   Operate on local container\n"
495                "     --no-convert          Don't convert keyboard mappings\n\n"
496                "Commands:\n"
497                "  status                   Show current locale settings\n"
498                "  set-locale LOCALE...     Set system locale\n"
499                "  list-locales             Show known locales\n"
500                "  set-keymap MAP [MAP]     Set console and X11 keyboard mappings\n"
501                "  list-keymaps             Show known virtual console keyboard mappings\n"
502                "  set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
503                "                           Set X11 and console keyboard mappings\n"
504                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
505                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
506                "  list-x11-keymap-variants [LAYOUT]\n"
507                "                           Show known X11 keyboard mapping variants\n"
508                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n"
509                , program_invocation_short_name);
510 }
511
512 static int parse_argv(int argc, char *argv[]) {
513
514         enum {
515                 ARG_VERSION = 0x100,
516                 ARG_NO_PAGER,
517                 ARG_NO_CONVERT,
518                 ARG_NO_ASK_PASSWORD
519         };
520
521         static const struct option options[] = {
522                 { "help",            no_argument,       NULL, 'h'                 },
523                 { "version",         no_argument,       NULL, ARG_VERSION         },
524                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
525                 { "host",            required_argument, NULL, 'H'                 },
526                 { "machine",         required_argument, NULL, 'M'                 },
527                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
528                 { "no-convert",      no_argument,       NULL, ARG_NO_CONVERT      },
529                 {}
530         };
531
532         int c;
533
534         assert(argc >= 0);
535         assert(argv);
536
537         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
538
539                 switch (c) {
540
541                 case 'h':
542                         help();
543                         return 0;
544
545                 case ARG_VERSION:
546                         puts(PACKAGE_STRING);
547                         puts(SYSTEMD_FEATURES);
548                         return 0;
549
550                 case ARG_NO_CONVERT:
551                         arg_convert = false;
552                         break;
553
554                 case ARG_NO_PAGER:
555                         arg_no_pager = true;
556                         break;
557
558                 case ARG_NO_ASK_PASSWORD:
559                         arg_ask_password = false;
560                         break;
561
562                 case 'H':
563                         arg_transport = BUS_TRANSPORT_REMOTE;
564                         arg_host = optarg;
565                         break;
566
567                 case 'M':
568                         arg_transport = BUS_TRANSPORT_MACHINE;
569                         arg_host = optarg;
570                         break;
571
572                 case '?':
573                         return -EINVAL;
574
575                 default:
576                         assert_not_reached("Unhandled option");
577                 }
578
579         return 1;
580 }
581
582 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
583
584         static const struct {
585                 const char* verb;
586                 const enum {
587                         MORE,
588                         LESS,
589                         EQUAL
590                 } argc_cmp;
591                 const int argc;
592                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
593         } verbs[] = {
594                 { "status",                   LESS,   1, show_status           },
595                 { "set-locale",               MORE,   2, set_locale            },
596                 { "list-locales",             EQUAL,  1, list_locales          },
597                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
598                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
599                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
600                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
601                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
602                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
603                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
604         };
605
606         int left;
607         unsigned i;
608
609         assert(argc >= 0);
610         assert(argv);
611
612         left = argc - optind;
613
614         if (left <= 0)
615                 /* Special rule: no arguments means "status" */
616                 i = 0;
617         else {
618                 if (streq(argv[optind], "help")) {
619                         help();
620                         return 0;
621                 }
622
623                 for (i = 0; i < ELEMENTSOF(verbs); i++)
624                         if (streq(argv[optind], verbs[i].verb))
625                                 break;
626
627                 if (i >= ELEMENTSOF(verbs)) {
628                         log_error("Unknown operation %s", argv[optind]);
629                         return -EINVAL;
630                 }
631         }
632
633         switch (verbs[i].argc_cmp) {
634
635         case EQUAL:
636                 if (left != verbs[i].argc) {
637                         log_error("Invalid number of arguments.");
638                         return -EINVAL;
639                 }
640
641                 break;
642
643         case MORE:
644                 if (left < verbs[i].argc) {
645                         log_error("Too few arguments.");
646                         return -EINVAL;
647                 }
648
649                 break;
650
651         case LESS:
652                 if (left > verbs[i].argc) {
653                         log_error("Too many arguments.");
654                         return -EINVAL;
655                 }
656
657                 break;
658
659         default:
660                 assert_not_reached("Unknown comparison operator.");
661         }
662
663         return verbs[i].dispatch(bus, argv + optind, left);
664 }
665
666 int main(int argc, char*argv[]) {
667         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
668         int r;
669
670         setlocale(LC_ALL, "");
671         log_parse_environment();
672         log_open();
673
674         r = parse_argv(argc, argv);
675         if (r <= 0)
676                 goto finish;
677
678         r = bus_open_transport(arg_transport, arg_host, false, &bus);
679         if (r < 0) {
680                 log_error_errno(r, "Failed to create bus connection: %m");
681                 goto finish;
682         }
683
684         r = localectl_main(bus, argc, argv);
685
686 finish:
687         pager_close();
688
689         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
690 }