chiark / gitweb /
journal: fix memleak, call set_free before return
[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                 return -EBADMSG;
358         }
359
360         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
361         for (i = 0; i < h->namehash_size; i++) {
362                 char *z;
363
364                 if (e[i].locrec_offset == 0)
365                         continue;
366
367                 z = strdup((char*) p + e[i].name_offset);
368                 if (!z) {
369                         r = log_oom();
370                         goto finish;
371                 }
372
373                 r = set_put(locales, z);
374                 if (r < 0) {
375                         free(z);
376                         log_error("Failed to add locale: %s", strerror(-r));
377                         goto finish;
378                 }
379         }
380
381         l = set_get_strv(locales);
382         if (!l) {
383                 r = log_oom();
384                 goto finish;
385         }
386
387         set_free(locales);
388         locales = NULL;
389
390         strv_sort(l);
391
392         pager_open_if_enabled();
393
394         STRV_FOREACH(j, l)
395                 puts(*j);
396
397         r = 0;
398
399 finish:
400         if (p != MAP_FAILED)
401                 munmap((void*) p, sz);
402
403         set_free_free(locales);
404
405         return r;
406 }
407
408 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
409         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
410         dbus_bool_t interactive = true, b;
411         const char *map, *toggle_map;
412
413         assert(bus);
414         assert(args);
415
416         if (n > 3) {
417                 log_error("Too many arguments.");
418                 return -EINVAL;
419         }
420
421         polkit_agent_open_if_enabled();
422
423         map = args[1];
424         toggle_map = n > 2 ? args[2] : "";
425         b = arg_convert;
426
427         return bus_method_call_with_reply(
428                         bus,
429                         "org.freedesktop.locale1",
430                         "/org/freedesktop/locale1",
431                         "org.freedesktop.locale1",
432                         "SetVConsoleKeyboard",
433                         &reply,
434                         NULL,
435                         DBUS_TYPE_STRING, &map,
436                         DBUS_TYPE_STRING, &toggle_map,
437                         DBUS_TYPE_BOOLEAN, &b,
438                         DBUS_TYPE_BOOLEAN, &interactive,
439                         DBUS_TYPE_INVALID);
440 }
441
442 static Set *keymaps = NULL;
443
444 static int nftw_cb(
445                 const char *fpath,
446                 const struct stat *sb,
447                 int tflag,
448                 struct FTW *ftwbuf) {
449
450         char *p, *e;
451         int r;
452
453         if (tflag != FTW_F)
454                 return 0;
455
456         if (!endswith(fpath, ".map") &&
457             !endswith(fpath, ".map.gz"))
458                 return 0;
459
460         p = strdup(path_get_file_name(fpath));
461         if (!p)
462                 return log_oom();
463
464         e = endswith(p, ".map");
465         if (e)
466                 *e = 0;
467
468         e = endswith(p, ".map.gz");
469         if (e)
470                 *e = 0;
471
472         r = set_put(keymaps, p);
473         if (r == -EEXIST)
474                 free(p);
475         else if (r < 0) {
476                 log_error("Can't add keymap: %s", strerror(-r));
477                 free(p);
478                 return r;
479         }
480
481         return 0;
482 }
483
484 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
485         char **l, **i;
486
487         keymaps = set_new(string_hash_func, string_compare_func);
488         if (!keymaps)
489                 return log_oom();
490
491         nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
492         nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
493         nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
494
495         l = set_get_strv(keymaps);
496         if (!l) {
497                 set_free_free(keymaps);
498                 return log_oom();
499         }
500
501         set_free(keymaps);
502
503         if (strv_isempty(l)) {
504                 log_error("Couldn't find any console keymaps.");
505                 return -ENOENT;
506         }
507
508         strv_sort(l);
509
510         pager_open_if_enabled();
511
512         STRV_FOREACH(i, l)
513                 puts(*i);
514
515         strv_free(l);
516
517         return 0;
518 }
519
520 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
521         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
522         dbus_bool_t interactive = true, b;
523         const char *layout, *model, *variant, *options;
524
525         assert(bus);
526         assert(args);
527
528         if (n > 5) {
529                 log_error("Too many arguments.");
530                 return -EINVAL;
531         }
532
533         polkit_agent_open_if_enabled();
534
535         layout = args[1];
536         model = n > 2 ? args[2] : "";
537         variant = n > 3 ? args[3] : "";
538         options = n > 3 ? args[4] : "";
539         b = arg_convert;
540
541         return bus_method_call_with_reply(
542                         bus,
543                         "org.freedesktop.locale1",
544                         "/org/freedesktop/locale1",
545                         "org.freedesktop.locale1",
546                         "SetX11Keyboard",
547                         &reply,
548                         NULL,
549                         DBUS_TYPE_STRING, &layout,
550                         DBUS_TYPE_STRING, &model,
551                         DBUS_TYPE_STRING, &variant,
552                         DBUS_TYPE_STRING, &options,
553                         DBUS_TYPE_BOOLEAN, &b,
554                         DBUS_TYPE_BOOLEAN, &interactive,
555                         DBUS_TYPE_INVALID);
556 }
557
558 static int help(void) {
559
560         printf("%s [OPTIONS...] COMMAND ...\n\n"
561                "Query or change system time and date settings.\n\n"
562                "  -h --help              Show this help\n"
563                "     --version           Show package version\n"
564                "     --no-convert        Don't convert keyboard mappings\n"
565                "     --no-pager          Do not pipe output into a pager\n"
566                "     --no-ask-password   Do not prompt for password\n"
567                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
568                "Commands:\n"
569                "  status                 Show current locale settings\n"
570                "  set-locale LOCALE...   Set system locale\n"
571                "  list-locales           Show known locales\n"
572                "  set-keymap MAP [MAP]   Set virtual console keyboard mapping\n"
573                "  list-keymaps           Show known virtual console keyboard mappings\n"
574                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
575                "                         Set X11 keyboard mapping\n",
576                program_invocation_short_name);
577
578         return 0;
579 }
580
581 static int parse_argv(int argc, char *argv[]) {
582
583         enum {
584                 ARG_VERSION = 0x100,
585                 ARG_NO_PAGER,
586                 ARG_NO_CONVERT,
587                 ARG_NO_ASK_PASSWORD
588         };
589
590         static const struct option options[] = {
591                 { "help",                no_argument,       NULL, 'h'                     },
592                 { "version",             no_argument,       NULL, ARG_VERSION             },
593                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
594                 { "host",                required_argument, NULL, 'H'                     },
595                 { "privileged",          no_argument,       NULL, 'P'                     },
596                 { "no-ask-password",     no_argument,       NULL, ARG_NO_ASK_PASSWORD     },
597                 { "no-convert",          no_argument,       NULL, ARG_NO_CONVERT          },
598                 { NULL,                  0,                 NULL, 0                       }
599         };
600
601         int c;
602
603         assert(argc >= 0);
604         assert(argv);
605
606         while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
607
608                 switch (c) {
609
610                 case 'h':
611                         help();
612                         return 0;
613
614                 case ARG_VERSION:
615                         puts(PACKAGE_STRING);
616                         puts(DISTRIBUTION);
617                         puts(SYSTEMD_FEATURES);
618                         return 0;
619
620                 case 'P':
621                         arg_transport = TRANSPORT_POLKIT;
622                         break;
623
624                 case 'H':
625                         arg_transport = TRANSPORT_SSH;
626                         arg_host = optarg;
627                         break;
628
629                 case ARG_NO_CONVERT:
630                         arg_convert = false;
631                         break;
632
633                 case ARG_NO_PAGER:
634                         arg_no_pager = true;
635                         break;
636
637                 case '?':
638                         return -EINVAL;
639
640                 default:
641                         log_error("Unknown option code %c", c);
642                         return -EINVAL;
643                 }
644         }
645
646         return 1;
647 }
648
649 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
650
651         static const struct {
652                 const char* verb;
653                 const enum {
654                         MORE,
655                         LESS,
656                         EQUAL
657                 } argc_cmp;
658                 const int argc;
659                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
660         } verbs[] = {
661                 { "status",         LESS,   1, show_status           },
662                 { "set-locale",     MORE,   2, set_locale            },
663                 { "list-locales",   EQUAL,  1, list_locales          },
664                 { "set-keymap",     MORE,   2, set_vconsole_keymap   },
665                 { "list-keymaps",   EQUAL,  1, list_vconsole_keymaps },
666                 { "set-x11-keymap", MORE,   2, set_x11_keymap        },
667         };
668
669         int left;
670         unsigned i;
671
672         assert(argc >= 0);
673         assert(argv);
674         assert(error);
675
676         left = argc - optind;
677
678         if (left <= 0)
679                 /* Special rule: no arguments means "status" */
680                 i = 0;
681         else {
682                 if (streq(argv[optind], "help")) {
683                         help();
684                         return 0;
685                 }
686
687                 for (i = 0; i < ELEMENTSOF(verbs); i++)
688                         if (streq(argv[optind], verbs[i].verb))
689                                 break;
690
691                 if (i >= ELEMENTSOF(verbs)) {
692                         log_error("Unknown operation %s", argv[optind]);
693                         return -EINVAL;
694                 }
695         }
696
697         switch (verbs[i].argc_cmp) {
698
699         case EQUAL:
700                 if (left != verbs[i].argc) {
701                         log_error("Invalid number of arguments.");
702                         return -EINVAL;
703                 }
704
705                 break;
706
707         case MORE:
708                 if (left < verbs[i].argc) {
709                         log_error("Too few arguments.");
710                         return -EINVAL;
711                 }
712
713                 break;
714
715         case LESS:
716                 if (left > verbs[i].argc) {
717                         log_error("Too many arguments.");
718                         return -EINVAL;
719                 }
720
721                 break;
722
723         default:
724                 assert_not_reached("Unknown comparison operator.");
725         }
726
727         if (!bus) {
728                 log_error("Failed to get D-Bus connection: %s", error->message);
729                 return -EIO;
730         }
731
732         return verbs[i].dispatch(bus, argv + optind, left);
733 }
734
735 int main(int argc, char *argv[]) {
736         int r, retval = EXIT_FAILURE;
737         DBusConnection *bus = NULL;
738         DBusError error;
739
740         dbus_error_init(&error);
741
742         log_parse_environment();
743         log_open();
744
745         r = parse_argv(argc, argv);
746         if (r < 0)
747                 goto finish;
748         else if (r == 0) {
749                 retval = EXIT_SUCCESS;
750                 goto finish;
751         }
752
753         if (arg_transport == TRANSPORT_NORMAL)
754                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
755         else if (arg_transport == TRANSPORT_POLKIT)
756                 bus_connect_system_polkit(&bus, &error);
757         else if (arg_transport == TRANSPORT_SSH)
758                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
759         else
760                 assert_not_reached("Uh, invalid transport...");
761
762         r = localectl_main(bus, argc, argv, &error);
763         retval = r < 0 ? EXIT_FAILURE : r;
764
765 finish:
766         if (bus) {
767                 dbus_connection_flush(bus);
768                 dbus_connection_close(bus);
769                 dbus_connection_unref(bus);
770         }
771
772         dbus_error_free(&error);
773         dbus_shutdown();
774
775         pager_close();
776
777         return retval;
778 }