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