chiark / gitweb /
treewide: yet more log_*_errno + return simplifications
[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                 log_error("Failed to open keyboard mapping list. %m");
406                 return -errno;
407         }
408
409         if (streq(args[0], "list-x11-keymap-models"))
410                 look_for = MODELS;
411         else if (streq(args[0], "list-x11-keymap-layouts"))
412                 look_for = LAYOUTS;
413         else if (streq(args[0], "list-x11-keymap-variants"))
414                 look_for = VARIANTS;
415         else if (streq(args[0], "list-x11-keymap-options"))
416                 look_for = OPTIONS;
417         else
418                 assert_not_reached("Wrong parameter");
419
420         FOREACH_LINE(line, f, break) {
421                 char *l, *w;
422
423                 l = strstrip(line);
424
425                 if (isempty(l))
426                         continue;
427
428                 if (l[0] == '!') {
429                         if (startswith(l, "! model"))
430                                 state = MODELS;
431                         else if (startswith(l, "! layout"))
432                                 state = LAYOUTS;
433                         else if (startswith(l, "! variant"))
434                                 state = VARIANTS;
435                         else if (startswith(l, "! option"))
436                                 state = OPTIONS;
437                         else
438                                 state = NONE;
439
440                         continue;
441                 }
442
443                 if (state != look_for)
444                         continue;
445
446                 w = l + strcspn(l, WHITESPACE);
447
448                 if (n > 1) {
449                         char *e;
450
451                         if (*w == 0)
452                                 continue;
453
454                         *w = 0;
455                         w++;
456                         w += strspn(w, WHITESPACE);
457
458                         e = strchr(w, ':');
459                         if (!e)
460                                 continue;
461
462                         *e = 0;
463
464                         if (!streq(w, args[1]))
465                                 continue;
466                 } else
467                         *w = 0;
468
469                  r = strv_extend(&list, l);
470                  if (r < 0)
471                          return log_oom();
472         }
473
474         if (strv_isempty(list)) {
475                 log_error("Couldn't find any entries.");
476                 return -ENOENT;
477         }
478
479         strv_sort(list);
480         strv_uniq(list);
481
482         pager_open_if_enabled();
483
484         strv_print(list);
485         return 0;
486 }
487
488 static void help(void) {
489         printf("%s [OPTIONS...] COMMAND ...\n\n"
490                "Query or change system locale and keyboard settings.\n\n"
491                "  -h --help                Show this help\n"
492                "     --version             Show package version\n"
493                "     --no-pager            Do not pipe output into a pager\n"
494                "     --no-ask-password     Do not prompt for password\n"
495                "  -H --host=[USER@]HOST    Operate on remote host\n"
496                "  -M --machine=CONTAINER   Operate on local container\n"
497                "     --no-convert          Don't convert keyboard mappings\n\n"
498                "Commands:\n"
499                "  status                   Show current locale settings\n"
500                "  set-locale LOCALE...     Set system locale\n"
501                "  list-locales             Show known locales\n"
502                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
503                "  list-keymaps             Show known virtual console keyboard mappings\n"
504                "  set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
505                "                           Set X11 keyboard mapping\n"
506                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
507                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
508                "  list-x11-keymap-variants [LAYOUT]\n"
509                "                           Show known X11 keyboard mapping variants\n"
510                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n"
511                , program_invocation_short_name);
512 }
513
514 static int parse_argv(int argc, char *argv[]) {
515
516         enum {
517                 ARG_VERSION = 0x100,
518                 ARG_NO_PAGER,
519                 ARG_NO_CONVERT,
520                 ARG_NO_ASK_PASSWORD
521         };
522
523         static const struct option options[] = {
524                 { "help",            no_argument,       NULL, 'h'                 },
525                 { "version",         no_argument,       NULL, ARG_VERSION         },
526                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
527                 { "host",            required_argument, NULL, 'H'                 },
528                 { "machine",         required_argument, NULL, 'M'                 },
529                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
530                 { "no-convert",      no_argument,       NULL, ARG_NO_CONVERT      },
531                 {}
532         };
533
534         int c;
535
536         assert(argc >= 0);
537         assert(argv);
538
539         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
540
541                 switch (c) {
542
543                 case 'h':
544                         help();
545                         return 0;
546
547                 case ARG_VERSION:
548                         puts(PACKAGE_STRING);
549                         puts(SYSTEMD_FEATURES);
550                         return 0;
551
552                 case ARG_NO_CONVERT:
553                         arg_convert = false;
554                         break;
555
556                 case ARG_NO_PAGER:
557                         arg_no_pager = true;
558                         break;
559
560                 case ARG_NO_ASK_PASSWORD:
561                         arg_ask_password = false;
562                         break;
563
564                 case 'H':
565                         arg_transport = BUS_TRANSPORT_REMOTE;
566                         arg_host = optarg;
567                         break;
568
569                 case 'M':
570                         arg_transport = BUS_TRANSPORT_CONTAINER;
571                         arg_host = optarg;
572                         break;
573
574                 case '?':
575                         return -EINVAL;
576
577                 default:
578                         assert_not_reached("Unhandled option");
579                 }
580
581         return 1;
582 }
583
584 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
585
586         static const struct {
587                 const char* verb;
588                 const enum {
589                         MORE,
590                         LESS,
591                         EQUAL
592                 } argc_cmp;
593                 const int argc;
594                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
595         } verbs[] = {
596                 { "status",                   LESS,   1, show_status           },
597                 { "set-locale",               MORE,   2, set_locale            },
598                 { "list-locales",             EQUAL,  1, list_locales          },
599                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
600                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
601                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
602                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
603                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
604                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
605                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
606         };
607
608         int left;
609         unsigned i;
610
611         assert(argc >= 0);
612         assert(argv);
613
614         left = argc - optind;
615
616         if (left <= 0)
617                 /* Special rule: no arguments means "status" */
618                 i = 0;
619         else {
620                 if (streq(argv[optind], "help")) {
621                         help();
622                         return 0;
623                 }
624
625                 for (i = 0; i < ELEMENTSOF(verbs); i++)
626                         if (streq(argv[optind], verbs[i].verb))
627                                 break;
628
629                 if (i >= ELEMENTSOF(verbs)) {
630                         log_error("Unknown operation %s", argv[optind]);
631                         return -EINVAL;
632                 }
633         }
634
635         switch (verbs[i].argc_cmp) {
636
637         case EQUAL:
638                 if (left != verbs[i].argc) {
639                         log_error("Invalid number of arguments.");
640                         return -EINVAL;
641                 }
642
643                 break;
644
645         case MORE:
646                 if (left < verbs[i].argc) {
647                         log_error("Too few arguments.");
648                         return -EINVAL;
649                 }
650
651                 break;
652
653         case LESS:
654                 if (left > verbs[i].argc) {
655                         log_error("Too many arguments.");
656                         return -EINVAL;
657                 }
658
659                 break;
660
661         default:
662                 assert_not_reached("Unknown comparison operator.");
663         }
664
665         return verbs[i].dispatch(bus, argv + optind, left);
666 }
667
668 int main(int argc, char*argv[]) {
669         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
670         int r;
671
672         setlocale(LC_ALL, "");
673         log_parse_environment();
674         log_open();
675
676         r = parse_argv(argc, argv);
677         if (r <= 0)
678                 goto finish;
679
680         r = bus_open_transport(arg_transport, arg_host, false, &bus);
681         if (r < 0) {
682                 log_error_errno(r, "Failed to create bus connection: %m");
683                 goto finish;
684         }
685
686         r = localectl_main(bus, argc, argv);
687
688 finish:
689         pager_close();
690
691         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
692 }