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