chiark / gitweb /
bus-proxyd: explicitly address messages to unique and well-known name
[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 = 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\n", locale_variable_to_string(j), variables[j]);
122
123                                 print_warning = false;
124                         } else
125                                 log_warning("                %s=%s\n", 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("Could not get properties: %s", strerror(-r));
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                 log_error("Failed to read list of locales: %s", strerror(-r));
239                 return r;
240         }
241
242         pager_open_if_enabled();
243         strv_print(l);
244
245         return 0;
246 }
247
248 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
249         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
250         const char *map, *toggle_map;
251         int r;
252
253         assert(bus);
254         assert(args);
255
256         if (n > 3) {
257                 log_error("Too many arguments.");
258                 return -EINVAL;
259         }
260
261         polkit_agent_open_if_enabled();
262
263         map = args[1];
264         toggle_map = n > 2 ? args[2] : "";
265
266         r = sd_bus_call_method(
267                         bus,
268                         "org.freedesktop.locale1",
269                         "/org/freedesktop/locale1",
270                         "org.freedesktop.locale1",
271                         "SetVConsoleKeyboard",
272                         &error,
273                         NULL,
274                         "ssbb", map, toggle_map, arg_convert, arg_ask_password);
275         if (r < 0)
276                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
277
278         return r;
279 }
280
281 static Set *keymaps = NULL;
282
283 static int nftw_cb(
284                 const char *fpath,
285                 const struct stat *sb,
286                 int tflag,
287                 struct FTW *ftwbuf) {
288
289         char *p, *e;
290         int r;
291
292         if (tflag != FTW_F)
293                 return 0;
294
295         if (!endswith(fpath, ".map") &&
296             !endswith(fpath, ".map.gz"))
297                 return 0;
298
299         p = strdup(basename(fpath));
300         if (!p)
301                 return log_oom();
302
303         e = endswith(p, ".map");
304         if (e)
305                 *e = 0;
306
307         e = endswith(p, ".map.gz");
308         if (e)
309                 *e = 0;
310
311         r = set_consume(keymaps, p);
312         if (r < 0 && r != -EEXIST) {
313                 log_error("Can't add keymap: %s", strerror(-r));
314                 return r;
315         }
316
317         return 0;
318 }
319
320 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
321         _cleanup_strv_free_ char **l = NULL;
322         const char *dir;
323
324         keymaps = set_new(&string_hash_ops);
325         if (!keymaps)
326                 return log_oom();
327
328         NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
329                 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
330
331         l = set_get_strv(keymaps);
332         if (!l) {
333                 set_free_free(keymaps);
334                 return log_oom();
335         }
336
337         set_free(keymaps);
338
339         if (strv_isempty(l)) {
340                 log_error("Couldn't find any console keymaps.");
341                 return -ENOENT;
342         }
343
344         strv_sort(l);
345
346         pager_open_if_enabled();
347
348         strv_print(l);
349
350         return 0;
351 }
352
353 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
354         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
355         const char *layout, *model, *variant, *options;
356         int r;
357
358         assert(bus);
359         assert(args);
360
361         if (n > 5) {
362                 log_error("Too many arguments.");
363                 return -EINVAL;
364         }
365
366         polkit_agent_open_if_enabled();
367
368         layout = args[1];
369         model = n > 2 ? args[2] : "";
370         variant = n > 3 ? args[3] : "";
371         options = n > 4 ? args[4] : "";
372
373         r = sd_bus_call_method(
374                         bus,
375                         "org.freedesktop.locale1",
376                         "/org/freedesktop/locale1",
377                         "org.freedesktop.locale1",
378                         "SetX11Keyboard",
379                         &error,
380                         NULL,
381                         "ssssbb", layout, model, variant, options,
382                                   arg_convert, arg_ask_password);
383         if (r < 0)
384                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
385
386         return r;
387 }
388
389 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
390         _cleanup_fclose_ FILE *f = NULL;
391         _cleanup_strv_free_ char **list = NULL;
392         char line[LINE_MAX];
393         enum {
394                 NONE,
395                 MODELS,
396                 LAYOUTS,
397                 VARIANTS,
398                 OPTIONS
399         } state = NONE, look_for;
400         int r;
401
402         if (n > 2) {
403                 log_error("Too many arguments.");
404                 return -EINVAL;
405         }
406
407         f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
408         if (!f) {
409                 log_error("Failed to open keyboard mapping list. %m");
410                 return -errno;
411         }
412
413         if (streq(args[0], "list-x11-keymap-models"))
414                 look_for = MODELS;
415         else if (streq(args[0], "list-x11-keymap-layouts"))
416                 look_for = LAYOUTS;
417         else if (streq(args[0], "list-x11-keymap-variants"))
418                 look_for = VARIANTS;
419         else if (streq(args[0], "list-x11-keymap-options"))
420                 look_for = OPTIONS;
421         else
422                 assert_not_reached("Wrong parameter");
423
424         FOREACH_LINE(line, f, break) {
425                 char *l, *w;
426
427                 l = strstrip(line);
428
429                 if (isempty(l))
430                         continue;
431
432                 if (l[0] == '!') {
433                         if (startswith(l, "! model"))
434                                 state = MODELS;
435                         else if (startswith(l, "! layout"))
436                                 state = LAYOUTS;
437                         else if (startswith(l, "! variant"))
438                                 state = VARIANTS;
439                         else if (startswith(l, "! option"))
440                                 state = OPTIONS;
441                         else
442                                 state = NONE;
443
444                         continue;
445                 }
446
447                 if (state != look_for)
448                         continue;
449
450                 w = l + strcspn(l, WHITESPACE);
451
452                 if (n > 1) {
453                         char *e;
454
455                         if (*w == 0)
456                                 continue;
457
458                         *w = 0;
459                         w++;
460                         w += strspn(w, WHITESPACE);
461
462                         e = strchr(w, ':');
463                         if (!e)
464                                 continue;
465
466                         *e = 0;
467
468                         if (!streq(w, args[1]))
469                                 continue;
470                 } else
471                         *w = 0;
472
473                  r = strv_extend(&list, l);
474                  if (r < 0)
475                          return log_oom();
476         }
477
478         if (strv_isempty(list)) {
479                 log_error("Couldn't find any entries.");
480                 return -ENOENT;
481         }
482
483         strv_sort(list);
484         strv_uniq(list);
485
486         pager_open_if_enabled();
487
488         strv_print(list);
489         return 0;
490 }
491
492 static void help(void) {
493         printf("%s [OPTIONS...] COMMAND ...\n\n"
494                "Query or change system locale and keyboard settings.\n\n"
495                "  -h --help                Show this help\n"
496                "     --version             Show package version\n"
497                "     --no-pager            Do not pipe output into a pager\n"
498                "     --no-ask-password     Do not prompt for password\n"
499                "  -H --host=[USER@]HOST    Operate on remote host\n"
500                "  -M --machine=CONTAINER   Operate on local container\n"
501                "     --no-convert          Don't convert keyboard mappings\n\n"
502                "Commands:\n"
503                "  status                   Show current locale settings\n"
504                "  set-locale LOCALE...     Set system locale\n"
505                "  list-locales             Show known locales\n"
506                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
507                "  list-keymaps             Show known virtual console keyboard mappings\n"
508                "  set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
509                "                           Set X11 keyboard mapping\n"
510                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
511                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
512                "  list-x11-keymap-variants [LAYOUT]\n"
513                "                           Show known X11 keyboard mapping variants\n"
514                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n"
515                , program_invocation_short_name);
516 }
517
518 static int parse_argv(int argc, char *argv[]) {
519
520         enum {
521                 ARG_VERSION = 0x100,
522                 ARG_NO_PAGER,
523                 ARG_NO_CONVERT,
524                 ARG_NO_ASK_PASSWORD
525         };
526
527         static const struct option options[] = {
528                 { "help",            no_argument,       NULL, 'h'                 },
529                 { "version",         no_argument,       NULL, ARG_VERSION         },
530                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
531                 { "host",            required_argument, NULL, 'H'                 },
532                 { "machine",         required_argument, NULL, 'M'                 },
533                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
534                 { "no-convert",      no_argument,       NULL, ARG_NO_CONVERT      },
535                 {}
536         };
537
538         int c;
539
540         assert(argc >= 0);
541         assert(argv);
542
543         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
544
545                 switch (c) {
546
547                 case 'h':
548                         help();
549                         return 0;
550
551                 case ARG_VERSION:
552                         puts(PACKAGE_STRING);
553                         puts(SYSTEMD_FEATURES);
554                         return 0;
555
556                 case ARG_NO_CONVERT:
557                         arg_convert = false;
558                         break;
559
560                 case ARG_NO_PAGER:
561                         arg_no_pager = true;
562                         break;
563
564                 case ARG_NO_ASK_PASSWORD:
565                         arg_ask_password = false;
566                         break;
567
568                 case 'H':
569                         arg_transport = BUS_TRANSPORT_REMOTE;
570                         arg_host = optarg;
571                         break;
572
573                 case 'M':
574                         arg_transport = BUS_TRANSPORT_CONTAINER;
575                         arg_host = optarg;
576                         break;
577
578                 case '?':
579                         return -EINVAL;
580
581                 default:
582                         assert_not_reached("Unhandled option");
583                 }
584
585         return 1;
586 }
587
588 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
589
590         static const struct {
591                 const char* verb;
592                 const enum {
593                         MORE,
594                         LESS,
595                         EQUAL
596                 } argc_cmp;
597                 const int argc;
598                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
599         } verbs[] = {
600                 { "status",                   LESS,   1, show_status           },
601                 { "set-locale",               MORE,   2, set_locale            },
602                 { "list-locales",             EQUAL,  1, list_locales          },
603                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
604                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
605                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
606                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
607                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
608                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
609                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
610         };
611
612         int left;
613         unsigned i;
614
615         assert(argc >= 0);
616         assert(argv);
617
618         left = argc - optind;
619
620         if (left <= 0)
621                 /* Special rule: no arguments means "status" */
622                 i = 0;
623         else {
624                 if (streq(argv[optind], "help")) {
625                         help();
626                         return 0;
627                 }
628
629                 for (i = 0; i < ELEMENTSOF(verbs); i++)
630                         if (streq(argv[optind], verbs[i].verb))
631                                 break;
632
633                 if (i >= ELEMENTSOF(verbs)) {
634                         log_error("Unknown operation %s", argv[optind]);
635                         return -EINVAL;
636                 }
637         }
638
639         switch (verbs[i].argc_cmp) {
640
641         case EQUAL:
642                 if (left != verbs[i].argc) {
643                         log_error("Invalid number of arguments.");
644                         return -EINVAL;
645                 }
646
647                 break;
648
649         case MORE:
650                 if (left < verbs[i].argc) {
651                         log_error("Too few arguments.");
652                         return -EINVAL;
653                 }
654
655                 break;
656
657         case LESS:
658                 if (left > verbs[i].argc) {
659                         log_error("Too many arguments.");
660                         return -EINVAL;
661                 }
662
663                 break;
664
665         default:
666                 assert_not_reached("Unknown comparison operator.");
667         }
668
669         return verbs[i].dispatch(bus, argv + optind, left);
670 }
671
672 int main(int argc, char*argv[]) {
673         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
674         int r;
675
676         setlocale(LC_ALL, "");
677         log_parse_environment();
678         log_open();
679
680         r = parse_argv(argc, argv);
681         if (r <= 0)
682                 goto finish;
683
684         r = bus_open_transport(arg_transport, arg_host, false, &bus);
685         if (r < 0) {
686                 log_error("Failed to create bus connection: %s", strerror(-r));
687                 goto finish;
688         }
689
690         r = localectl_main(bus, argc, argv);
691
692 finish:
693         pager_close();
694
695         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
696 }