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