chiark / gitweb /
7d3ac0ad2fe52dd437b518d367ef1a0375b56a93
[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
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <string.h>
27 #include <ftw.h>
28 #include <sys/mman.h>
29 #include <fcntl.h>
30
31 #include "dbus-common.h"
32 #include "util.h"
33 #include "spawn-polkit-agent.h"
34 #include "build.h"
35 #include "strv.h"
36 #include "pager.h"
37 #include "set.h"
38 #include "path-util.h"
39
40 static bool arg_no_pager = false;
41 static enum transport {
42         TRANSPORT_NORMAL,
43         TRANSPORT_SSH,
44         TRANSPORT_POLKIT
45 } arg_transport = TRANSPORT_NORMAL;
46 static bool arg_ask_password = true;
47 static const char *arg_host = NULL;
48 static bool arg_convert = true;
49
50 static void pager_open_if_enabled(void) {
51
52         if (arg_no_pager)
53                 return;
54
55         pager_open();
56 }
57
58 static void polkit_agent_open_if_enabled(void) {
59
60         /* Open the polkit agent as a child process if necessary */
61
62         if (!arg_ask_password)
63                 return;
64
65         polkit_agent_open();
66 }
67
68 typedef struct StatusInfo {
69         char **locale;
70         const char *vconsole_keymap;
71         const char *vconsole_keymap_toggle;
72         const char *x11_layout;
73         const char *x11_model;
74         const char *x11_variant;
75         const char *x11_options;
76 } StatusInfo;
77
78 static void print_status_info(StatusInfo *i) {
79         assert(i);
80
81         if (strv_isempty(i->locale))
82                 puts("   System Locale: n/a\n");
83         else {
84                 char **j;
85
86                 printf("   System Locale: %s\n", i->locale[0]);
87                 STRV_FOREACH(j, i->locale + 1)
88                         printf("                  %s\n", *j);
89         }
90
91         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
92         if (!isempty(i->vconsole_keymap_toggle))
93                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
94
95         printf("      X11 Layout: %s\n", strna(i->x11_layout));
96         if (!isempty(i->x11_model))
97                 printf("       X11 Model: %s\n", i->x11_model);
98         if (!isempty(i->x11_variant))
99                 printf("     X11 Variant: %s\n", i->x11_variant);
100         if (!isempty(i->x11_options))
101                 printf("     X11 Options: %s\n", i->x11_options);
102 }
103
104 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
105         int r;
106
107         assert(name);
108         assert(iter);
109
110         switch (dbus_message_iter_get_arg_type(iter)) {
111
112         case DBUS_TYPE_STRING: {
113                 const char *s;
114
115                 dbus_message_iter_get_basic(iter, &s);
116                 if (!isempty(s)) {
117                         if (streq(name, "VConsoleKeymap"))
118                                 i->vconsole_keymap = s;
119                         else if (streq(name, "VConsoleKeymapToggle"))
120                                 i->vconsole_keymap_toggle = s;
121                         else if (streq(name, "X11Layout"))
122                                 i->x11_layout = s;
123                         else if (streq(name, "X11Model"))
124                                 i->x11_model = s;
125                         else if (streq(name, "X11Variant"))
126                                 i->x11_variant = s;
127                         else if (streq(name, "X11Options"))
128                                 i->x11_options = s;
129                 }
130                 break;
131         }
132
133         case DBUS_TYPE_ARRAY:
134
135                 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
136                         char **l;
137
138                         r = bus_parse_strv_iter(iter, &l);
139                         if (r < 0)
140                                 return r;
141
142                         if (streq(name, "Locale")) {
143                                 strv_free(i->locale);
144                                 i->locale = l;
145                                 l = NULL;
146                         }
147
148                         strv_free(l);
149                 }
150         }
151
152         return 0;
153 }
154
155 static int show_status(DBusConnection *bus, char **args, unsigned n) {
156         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
157         const char *interface = "";
158         int r;
159         DBusMessageIter iter, sub, sub2, sub3;
160         StatusInfo info;
161
162         assert(args);
163
164         r = bus_method_call_with_reply(
165                         bus,
166                         "org.freedesktop.locale1",
167                         "/org/freedesktop/locale1",
168                         "org.freedesktop.DBus.Properties",
169                         "GetAll",
170                         &reply,
171                         NULL,
172                         DBUS_TYPE_STRING, &interface,
173                         DBUS_TYPE_INVALID);
174         if (r < 0)
175                 return r;
176
177         if (!dbus_message_iter_init(reply, &iter) ||
178             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
179             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
180                 log_error("Failed to parse reply.");
181                 return -EIO;
182         }
183
184         zero(info);
185         dbus_message_iter_recurse(&iter, &sub);
186
187         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
188                 const char *name;
189
190                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
191                         log_error("Failed to parse reply.");
192                         return -EIO;
193                 }
194
195                 dbus_message_iter_recurse(&sub, &sub2);
196
197                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
198                         log_error("Failed to parse reply.");
199                         return -EIO;
200                 }
201
202                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
203                         log_error("Failed to parse reply.");
204                         return -EIO;
205                 }
206
207                 dbus_message_iter_recurse(&sub2, &sub3);
208
209                 r = status_property(name, &sub3, &info);
210                 if (r < 0) {
211                         log_error("Failed to parse reply.");
212                         return r;
213                 }
214
215                 dbus_message_iter_next(&sub);
216         }
217
218         print_status_info(&info);
219         strv_free(info.locale);
220         return 0;
221 }
222
223 static int set_locale(DBusConnection *bus, char **args, unsigned n) {
224         _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
225         dbus_bool_t interactive = true;
226         DBusError error;
227         DBusMessageIter iter;
228         int r;
229
230         assert(bus);
231         assert(args);
232
233         dbus_error_init(&error);
234
235         polkit_agent_open_if_enabled();
236
237         m = dbus_message_new_method_call(
238                         "org.freedesktop.locale1",
239                         "/org/freedesktop/locale1",
240                         "org.freedesktop.locale1",
241                         "SetLocale");
242         if (!m)
243                 return log_oom();
244
245         dbus_message_iter_init_append(m, &iter);
246
247         r = bus_append_strv_iter(&iter, args + 1);
248         if (r < 0)
249                 return log_oom();
250
251         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &interactive))
252                 return log_oom();
253
254         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
255         if (!reply) {
256                 log_error("Failed to issue method call: %s", bus_error_message(&error));
257                 r = -EIO;
258                 goto finish;
259         }
260
261         r = 0;
262
263 finish:
264         dbus_error_free(&error);
265         return r;
266 }
267
268 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
269         /* Stolen from glibc... */
270
271         struct locarhead {
272                 uint32_t magic;
273                 /* Serial number.  */
274                 uint32_t serial;
275                 /* Name hash table.  */
276                 uint32_t namehash_offset;
277                 uint32_t namehash_used;
278                 uint32_t namehash_size;
279                 /* String table.  */
280                 uint32_t string_offset;
281                 uint32_t string_used;
282                 uint32_t string_size;
283                 /* Table with locale records.  */
284                 uint32_t locrectab_offset;
285                 uint32_t locrectab_used;
286                 uint32_t locrectab_size;
287                 /* MD5 sum hash table.  */
288                 uint32_t sumhash_offset;
289                 uint32_t sumhash_used;
290                 uint32_t sumhash_size;
291         };
292
293         struct namehashent {
294                 /* Hash value of the name.  */
295                 uint32_t hashval;
296                 /* Offset of the name in the string table.  */
297                 uint32_t name_offset;
298                 /* Offset of the locale record.  */
299                 uint32_t locrec_offset;
300         };
301
302         const struct locarhead *h;
303         const struct namehashent *e;
304         const void *p = MAP_FAILED;
305         _cleanup_close_ int fd = -1;
306         _cleanup_strv_free_ char **l = NULL;
307         char **j;
308         Set *locales;
309         size_t sz = 0;
310         struct stat st;
311         unsigned i;
312         int r;
313
314         locales = set_new(string_hash_func, string_compare_func);
315         if (!locales)
316                 return log_oom();
317
318         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
319         if (fd < 0) {
320                 log_error("Failed to open locale archive: %m");
321                 r = -errno;
322                 goto finish;
323         }
324
325         if (fstat(fd, &st) < 0) {
326                 log_error("fstat() failed: %m");
327                 r = -errno;
328                 goto finish;
329         }
330
331         if (!S_ISREG(st.st_mode)) {
332                 log_error("Archive file is not regular");
333                 r = -EBADMSG;
334                 goto finish;
335         }
336
337         if (st.st_size < (off_t) sizeof(struct locarhead)) {
338                 log_error("Archive has invalid size");
339                 r = -EBADMSG;
340                 goto finish;
341         }
342
343         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
344         if (p == MAP_FAILED) {
345                 log_error("Failed to map archive: %m");
346                 r = -errno;
347                 goto finish;
348         }
349
350         h = (const struct locarhead *) p;
351         if (h->magic != 0xde020109 ||
352             h->namehash_offset + h->namehash_size > st.st_size ||
353             h->string_offset + h->string_size > st.st_size ||
354             h->locrectab_offset + h->locrectab_size > st.st_size ||
355             h->sumhash_offset + h->sumhash_size > st.st_size) {
356                 log_error("Invalid archive file.");
357                 r = -EBADMSG;
358                 goto finish;
359         }
360
361         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
362         for (i = 0; i < h->namehash_size; i++) {
363                 char *z;
364
365                 if (e[i].locrec_offset == 0)
366                         continue;
367
368                 z = strdup((char*) p + e[i].name_offset);
369                 if (!z) {
370                         r = log_oom();
371                         goto finish;
372                 }
373
374                 r = set_put(locales, z);
375                 if (r < 0) {
376                         free(z);
377                         log_error("Failed to add locale: %s", strerror(-r));
378                         goto finish;
379                 }
380         }
381
382         l = set_get_strv(locales);
383         if (!l) {
384                 r = log_oom();
385                 goto finish;
386         }
387
388         set_free(locales);
389         locales = NULL;
390
391         strv_sort(l);
392
393         pager_open_if_enabled();
394
395         STRV_FOREACH(j, l)
396                 puts(*j);
397
398         r = 0;
399
400 finish:
401         if (p != MAP_FAILED)
402                 munmap((void*) p, sz);
403
404         set_free_free(locales);
405
406         return r;
407 }
408
409 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
410         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
411         dbus_bool_t interactive = true, b;
412         const char *map, *toggle_map;
413
414         assert(bus);
415         assert(args);
416
417         if (n > 3) {
418                 log_error("Too many arguments.");
419                 return -EINVAL;
420         }
421
422         polkit_agent_open_if_enabled();
423
424         map = args[1];
425         toggle_map = n > 2 ? args[2] : "";
426         b = arg_convert;
427
428         return bus_method_call_with_reply(
429                         bus,
430                         "org.freedesktop.locale1",
431                         "/org/freedesktop/locale1",
432                         "org.freedesktop.locale1",
433                         "SetVConsoleKeyboard",
434                         &reply,
435                         NULL,
436                         DBUS_TYPE_STRING, &map,
437                         DBUS_TYPE_STRING, &toggle_map,
438                         DBUS_TYPE_BOOLEAN, &b,
439                         DBUS_TYPE_BOOLEAN, &interactive,
440                         DBUS_TYPE_INVALID);
441 }
442
443 static Set *keymaps = NULL;
444
445 static int nftw_cb(
446                 const char *fpath,
447                 const struct stat *sb,
448                 int tflag,
449                 struct FTW *ftwbuf) {
450
451         char *p, *e;
452         int r;
453
454         if (tflag != FTW_F)
455                 return 0;
456
457         if (!endswith(fpath, ".map") &&
458             !endswith(fpath, ".map.gz"))
459                 return 0;
460
461         p = strdup(path_get_file_name(fpath));
462         if (!p)
463                 return log_oom();
464
465         e = endswith(p, ".map");
466         if (e)
467                 *e = 0;
468
469         e = endswith(p, ".map.gz");
470         if (e)
471                 *e = 0;
472
473         r = set_put(keymaps, p);
474         if (r == -EEXIST)
475                 free(p);
476         else if (r < 0) {
477                 log_error("Can't add keymap: %s", strerror(-r));
478                 free(p);
479                 return r;
480         }
481
482         return 0;
483 }
484
485 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
486         char _cleanup_strv_free_ **l = NULL;
487         char **i;
488
489         keymaps = set_new(string_hash_func, string_compare_func);
490         if (!keymaps)
491                 return log_oom();
492
493         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
494         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
495         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
496
497         l = set_get_strv(keymaps);
498         if (!l) {
499                 set_free_free(keymaps);
500                 return log_oom();
501         }
502
503         set_free(keymaps);
504
505         if (strv_isempty(l)) {
506                 log_error("Couldn't find any console keymaps.");
507                 return -ENOENT;
508         }
509
510         strv_sort(l);
511
512         pager_open_if_enabled();
513
514         STRV_FOREACH(i, l)
515                 puts(*i);
516
517
518         return 0;
519 }
520
521 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
522         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
523         dbus_bool_t interactive = true, b;
524         const char *layout, *model, *variant, *options;
525
526         assert(bus);
527         assert(args);
528
529         if (n > 5) {
530                 log_error("Too many arguments.");
531                 return -EINVAL;
532         }
533
534         polkit_agent_open_if_enabled();
535
536         layout = args[1];
537         model = n > 2 ? args[2] : "";
538         variant = n > 3 ? args[3] : "";
539         options = n > 3 ? args[4] : "";
540         b = arg_convert;
541
542         return bus_method_call_with_reply(
543                         bus,
544                         "org.freedesktop.locale1",
545                         "/org/freedesktop/locale1",
546                         "org.freedesktop.locale1",
547                         "SetX11Keyboard",
548                         &reply,
549                         NULL,
550                         DBUS_TYPE_STRING, &layout,
551                         DBUS_TYPE_STRING, &model,
552                         DBUS_TYPE_STRING, &variant,
553                         DBUS_TYPE_STRING, &options,
554                         DBUS_TYPE_BOOLEAN, &b,
555                         DBUS_TYPE_BOOLEAN, &interactive,
556                         DBUS_TYPE_INVALID);
557 }
558
559 static int help(void) {
560
561         printf("%s [OPTIONS...] COMMAND ...\n\n"
562                "Query or change system time and date settings.\n\n"
563                "  -h --help              Show this help\n"
564                "     --version           Show package version\n"
565                "     --no-convert        Don't convert keyboard mappings\n"
566                "     --no-pager          Do not pipe output into a pager\n"
567                "     --no-ask-password   Do not prompt for password\n"
568                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
569                "Commands:\n"
570                "  status                 Show current locale settings\n"
571                "  set-locale LOCALE...   Set system locale\n"
572                "  list-locales           Show known locales\n"
573                "  set-keymap MAP [MAP]   Set virtual console keyboard mapping\n"
574                "  list-keymaps           Show known virtual console keyboard mappings\n"
575                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
576                "                         Set X11 keyboard mapping\n",
577                program_invocation_short_name);
578
579         return 0;
580 }
581
582 static int parse_argv(int argc, char *argv[]) {
583
584         enum {
585                 ARG_VERSION = 0x100,
586                 ARG_NO_PAGER,
587                 ARG_NO_CONVERT,
588                 ARG_NO_ASK_PASSWORD
589         };
590
591         static const struct option options[] = {
592                 { "help",                no_argument,       NULL, 'h'                     },
593                 { "version",             no_argument,       NULL, ARG_VERSION             },
594                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
595                 { "host",                required_argument, NULL, 'H'                     },
596                 { "privileged",          no_argument,       NULL, 'P'                     },
597                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
598                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
599                 { NULL,                  0,                 NULL, 0                       }
600         };
601
602         int c;
603
604         assert(argc >= 0);
605         assert(argv);
606
607         while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
608
609                 switch (c) {
610
611                 case 'h':
612                         help();
613                         return 0;
614
615                 case ARG_VERSION:
616                         puts(PACKAGE_STRING);
617                         puts(DISTRIBUTION);
618                         puts(SYSTEMD_FEATURES);
619                         return 0;
620
621                 case 'P':
622                         arg_transport = TRANSPORT_POLKIT;
623                         break;
624
625                 case 'H':
626                         arg_transport = TRANSPORT_SSH;
627                         arg_host = optarg;
628                         break;
629
630                 case ARG_NO_CONVERT:
631                         arg_convert = false;
632                         break;
633
634                 case ARG_NO_PAGER:
635                         arg_no_pager = true;
636                         break;
637
638                 case '?':
639                         return -EINVAL;
640
641                 default:
642                         log_error("Unknown option code %c", c);
643                         return -EINVAL;
644                 }
645         }
646
647         return 1;
648 }
649
650 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
651
652         static const struct {
653                 const char* verb;
654                 const enum {
655                         MORE,
656                         LESS,
657                         EQUAL
658                 } argc_cmp;
659                 const int argc;
660                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
661         } verbs[] = {
662                 { "status",         LESS,   1, show_status           },
663                 { "set-locale",     MORE,   2, set_locale            },
664                 { "list-locales",   EQUAL,  1, list_locales          },
665                 { "set-keymap",     MORE,   2, set_vconsole_keymap   },
666                 { "list-keymaps",   EQUAL,  1, list_vconsole_keymaps },
667                 { "set-x11-keymap", MORE,   2, set_x11_keymap        },
668         };
669
670         int left;
671         unsigned i;
672
673         assert(argc >= 0);
674         assert(argv);
675         assert(error);
676
677         left = argc - optind;
678
679         if (left <= 0)
680                 /* Special rule: no arguments means "status" */
681                 i = 0;
682         else {
683                 if (streq(argv[optind], "help")) {
684                         help();
685                         return 0;
686                 }
687
688                 for (i = 0; i < ELEMENTSOF(verbs); i++)
689                         if (streq(argv[optind], verbs[i].verb))
690                                 break;
691
692                 if (i >= ELEMENTSOF(verbs)) {
693                         log_error("Unknown operation %s", argv[optind]);
694                         return -EINVAL;
695                 }
696         }
697
698         switch (verbs[i].argc_cmp) {
699
700         case EQUAL:
701                 if (left != verbs[i].argc) {
702                         log_error("Invalid number of arguments.");
703                         return -EINVAL;
704                 }
705
706                 break;
707
708         case MORE:
709                 if (left < verbs[i].argc) {
710                         log_error("Too few arguments.");
711                         return -EINVAL;
712                 }
713
714                 break;
715
716         case LESS:
717                 if (left > verbs[i].argc) {
718                         log_error("Too many arguments.");
719                         return -EINVAL;
720                 }
721
722                 break;
723
724         default:
725                 assert_not_reached("Unknown comparison operator.");
726         }
727
728         if (!bus) {
729                 log_error("Failed to get D-Bus connection: %s", error->message);
730                 return -EIO;
731         }
732
733         return verbs[i].dispatch(bus, argv + optind, left);
734 }
735
736 int main(int argc, char *argv[]) {
737         int r, retval = EXIT_FAILURE;
738         DBusConnection *bus = NULL;
739         DBusError error;
740
741         dbus_error_init(&error);
742
743         log_parse_environment();
744         log_open();
745
746         r = parse_argv(argc, argv);
747         if (r < 0)
748                 goto finish;
749         else if (r == 0) {
750                 retval = EXIT_SUCCESS;
751                 goto finish;
752         }
753
754         if (arg_transport == TRANSPORT_NORMAL)
755                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
756         else if (arg_transport == TRANSPORT_POLKIT)
757                 bus_connect_system_polkit(&bus, &error);
758         else if (arg_transport == TRANSPORT_SSH)
759                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
760         else
761                 assert_not_reached("Uh, invalid transport...");
762
763         r = localectl_main(bus, argc, argv, &error);
764         retval = r < 0 ? EXIT_FAILURE : r;
765
766 finish:
767         if (bus) {
768                 dbus_connection_flush(bus);
769                 dbus_connection_close(bus);
770                 dbus_connection_unref(bus);
771         }
772
773         dbus_error_free(&error);
774         dbus_shutdown();
775
776         pager_close();
777
778         return retval;
779 }