chiark / gitweb /
Unify parse_argv style
[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 void help(void) {
444         printf("%s [OPTIONS...] COMMAND ...\n\n"
445                "Query or change system locale and keyboard settings.\n\n"
446                "  -h --help                Show this help\n"
447                "     --version             Show package version\n"
448                "     --no-pager            Do not pipe output into a pager\n"
449                "     --no-ask-password     Do not prompt for password\n"
450                "  -H --host=[USER@]HOST    Operate on remote host\n"
451                "  -M --machine=CONTAINER   Operate on local container\n"
452                "     --no-convert          Don't convert keyboard mappings\n\n"
453                "Commands:\n"
454                "  status                   Show current locale settings\n"
455                "  set-locale LOCALE...     Set system locale\n"
456                "  list-locales             Show known locales\n"
457                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
458                "  list-keymaps             Show known virtual console keyboard mappings\n"
459                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
460                "                           Set X11 keyboard mapping\n"
461                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
462                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
463                "  list-x11-keymap-variants [LAYOUT]\n"
464                "                           Show known X11 keyboard mapping variants\n"
465                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n"
466                , program_invocation_short_name);
467 }
468
469 static int parse_argv(int argc, char *argv[]) {
470
471         enum {
472                 ARG_VERSION = 0x100,
473                 ARG_NO_PAGER,
474                 ARG_NO_CONVERT,
475                 ARG_NO_ASK_PASSWORD
476         };
477
478         static const struct option options[] = {
479                 { "help",            no_argument,       NULL, 'h'                 },
480                 { "version",         no_argument,       NULL, ARG_VERSION         },
481                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
482                 { "host",            required_argument, NULL, 'H'                 },
483                 { "machine",         required_argument, NULL, 'M'                 },
484                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
485                 { "no-convert",      no_argument,       NULL, ARG_NO_CONVERT      },
486                 {}
487         };
488
489         int c;
490
491         assert(argc >= 0);
492         assert(argv);
493
494         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
495
496                 switch (c) {
497
498                 case 'h':
499                         help();
500                         return 0;
501
502                 case ARG_VERSION:
503                         puts(PACKAGE_STRING);
504                         puts(SYSTEMD_FEATURES);
505                         return 0;
506
507                 case ARG_NO_CONVERT:
508                         arg_convert = false;
509                         break;
510
511                 case ARG_NO_PAGER:
512                         arg_no_pager = true;
513                         break;
514
515                 case ARG_NO_ASK_PASSWORD:
516                         arg_ask_password = false;
517                         break;
518
519                 case 'H':
520                         arg_transport = BUS_TRANSPORT_REMOTE;
521                         arg_host = optarg;
522                         break;
523
524                 case 'M':
525                         arg_transport = BUS_TRANSPORT_CONTAINER;
526                         arg_host = optarg;
527                         break;
528
529                 case '?':
530                         return -EINVAL;
531
532                 default:
533                         assert_not_reached("Unhandled option");
534                 }
535
536         return 1;
537 }
538
539 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
540
541         static const struct {
542                 const char* verb;
543                 const enum {
544                         MORE,
545                         LESS,
546                         EQUAL
547                 } argc_cmp;
548                 const int argc;
549                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
550         } verbs[] = {
551                 { "status",                   LESS,   1, show_status           },
552                 { "set-locale",               MORE,   2, set_locale            },
553                 { "list-locales",             EQUAL,  1, list_locales          },
554                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
555                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
556                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
557                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
558                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
559                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
560                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
561         };
562
563         int left;
564         unsigned i;
565
566         assert(argc >= 0);
567         assert(argv);
568
569         left = argc - optind;
570
571         if (left <= 0)
572                 /* Special rule: no arguments means "status" */
573                 i = 0;
574         else {
575                 if (streq(argv[optind], "help")) {
576                         help();
577                         return 0;
578                 }
579
580                 for (i = 0; i < ELEMENTSOF(verbs); i++)
581                         if (streq(argv[optind], verbs[i].verb))
582                                 break;
583
584                 if (i >= ELEMENTSOF(verbs)) {
585                         log_error("Unknown operation %s", argv[optind]);
586                         return -EINVAL;
587                 }
588         }
589
590         switch (verbs[i].argc_cmp) {
591
592         case EQUAL:
593                 if (left != verbs[i].argc) {
594                         log_error("Invalid number of arguments.");
595                         return -EINVAL;
596                 }
597
598                 break;
599
600         case MORE:
601                 if (left < verbs[i].argc) {
602                         log_error("Too few arguments.");
603                         return -EINVAL;
604                 }
605
606                 break;
607
608         case LESS:
609                 if (left > verbs[i].argc) {
610                         log_error("Too many arguments.");
611                         return -EINVAL;
612                 }
613
614                 break;
615
616         default:
617                 assert_not_reached("Unknown comparison operator.");
618         }
619
620         return verbs[i].dispatch(bus, argv + optind, left);
621 }
622
623 int main(int argc, char*argv[]) {
624         _cleanup_bus_unref_ sd_bus *bus = NULL;
625         int r;
626
627         setlocale(LC_ALL, "");
628         log_parse_environment();
629         log_open();
630
631         r = parse_argv(argc, argv);
632         if (r <= 0)
633                 goto finish;
634
635         r = bus_open_transport(arg_transport, arg_host, false, &bus);
636         if (r < 0) {
637                 log_error("Failed to create bus connection: %s", strerror(-r));
638                 goto finish;
639         }
640
641         r = localectl_main(bus, argc, argv);
642
643 finish:
644         pager_close();
645
646         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
647 }