]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localectl.c
Add set_consume which always takes ownership
[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 #include "utf8.h"
41
42 static bool arg_no_pager = false;
43 static enum transport {
44 TRANSPORT_NORMAL,
45 TRANSPORT_SSH,
46 TRANSPORT_POLKIT
47 } arg_transport = TRANSPORT_NORMAL;
48 static bool arg_ask_password = true;
49 static const char *arg_host = NULL;
50 static bool arg_convert = true;
51
52 static void pager_open_if_enabled(void) {
53
54 if (arg_no_pager)
55 return;
56
57 pager_open(false);
58 }
59
60 static void polkit_agent_open_if_enabled(void) {
61
62 /* Open the polkit agent as a child process if necessary */
63
64 if (!arg_ask_password)
65 return;
66
67 polkit_agent_open();
68 }
69
70 typedef struct StatusInfo {
71 char **locale;
72 const char *vconsole_keymap;
73 const char *vconsole_keymap_toggle;
74 const char *x11_layout;
75 const char *x11_model;
76 const char *x11_variant;
77 const char *x11_options;
78 } StatusInfo;
79
80 static void print_status_info(StatusInfo *i) {
81 assert(i);
82
83 if (strv_isempty(i->locale))
84 puts(" System Locale: n/a\n");
85 else {
86 char **j;
87
88 printf(" System Locale: %s\n", i->locale[0]);
89 STRV_FOREACH(j, i->locale + 1)
90 printf(" %s\n", *j);
91 }
92
93 printf(" VC Keymap: %s\n", strna(i->vconsole_keymap));
94 if (!isempty(i->vconsole_keymap_toggle))
95 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
96
97 printf(" X11 Layout: %s\n", strna(i->x11_layout));
98 if (!isempty(i->x11_model))
99 printf(" X11 Model: %s\n", i->x11_model);
100 if (!isempty(i->x11_variant))
101 printf(" X11 Variant: %s\n", i->x11_variant);
102 if (!isempty(i->x11_options))
103 printf(" X11 Options: %s\n", i->x11_options);
104 }
105
106 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
107 int r;
108
109 assert(name);
110 assert(iter);
111
112 switch (dbus_message_iter_get_arg_type(iter)) {
113
114 case DBUS_TYPE_STRING: {
115 const char *s;
116
117 dbus_message_iter_get_basic(iter, &s);
118 if (!isempty(s)) {
119 if (streq(name, "VConsoleKeymap"))
120 i->vconsole_keymap = s;
121 else if (streq(name, "VConsoleKeymapToggle"))
122 i->vconsole_keymap_toggle = s;
123 else if (streq(name, "X11Layout"))
124 i->x11_layout = s;
125 else if (streq(name, "X11Model"))
126 i->x11_model = s;
127 else if (streq(name, "X11Variant"))
128 i->x11_variant = s;
129 else if (streq(name, "X11Options"))
130 i->x11_options = s;
131 }
132 break;
133 }
134
135 case DBUS_TYPE_ARRAY:
136
137 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
138 char **l;
139
140 r = bus_parse_strv_iter(iter, &l);
141 if (r < 0)
142 return r;
143
144 if (streq(name, "Locale")) {
145 strv_free(i->locale);
146 i->locale = l;
147 l = NULL;
148 }
149
150 strv_free(l);
151 }
152 }
153
154 return 0;
155 }
156
157 static int show_status(DBusConnection *bus, char **args, unsigned n) {
158 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
159 const char *interface = "";
160 int r;
161 DBusMessageIter iter, sub, sub2, sub3;
162 StatusInfo info = {};
163
164 assert(args);
165
166 r = bus_method_call_with_reply(
167 bus,
168 "org.freedesktop.locale1",
169 "/org/freedesktop/locale1",
170 "org.freedesktop.DBus.Properties",
171 "GetAll",
172 &reply,
173 NULL,
174 DBUS_TYPE_STRING, &interface,
175 DBUS_TYPE_INVALID);
176 if (r < 0)
177 return r;
178
179 if (!dbus_message_iter_init(reply, &iter) ||
180 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
181 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
182 log_error("Failed to parse reply.");
183 return -EIO;
184 }
185
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 if (!utf8_is_valid((char*) p + e[i].name_offset))
364 continue;
365
366 z = strdup((char*) p + e[i].name_offset);
367 if (!z) {
368 r = log_oom();
369 goto finish;
370 }
371
372 r = set_consume(locales, z);
373 if (r < 0) {
374 log_error("Failed to add locale: %s", strerror(-r));
375 goto finish;
376 }
377 }
378
379 r = 0;
380
381 finish:
382 if (p != MAP_FAILED)
383 munmap((void*) p, sz);
384
385 return r;
386 }
387
388 static int add_locales_from_libdir (Set *locales) {
389 _cleanup_closedir_ DIR *dir;
390 struct dirent *entry;
391 int r;
392
393 dir = opendir("/usr/lib/locale");
394 if (!dir) {
395 log_error("Failed to open locale directory: %m");
396 return -errno;
397 }
398
399 errno = 0;
400 while ((entry = readdir(dir))) {
401 char *z;
402
403 if (entry->d_type != DT_DIR)
404 continue;
405
406 if (ignore_file(entry->d_name))
407 continue;
408
409 z = strdup(entry->d_name);
410 if (!z)
411 return log_oom();
412
413 r = set_consume(locales, z);
414 if (r < 0 && r != -EEXIST) {
415 log_error("Failed to add locale: %s", strerror(-r));
416 return r;
417 }
418
419 errno = 0;
420 }
421
422 if (errno > 0) {
423 log_error("Failed to read locale directory: %m");
424 return -errno;
425 }
426
427 return 0;
428 }
429
430 static int list_locales(DBusConnection *bus, char **args, unsigned n) {
431 _cleanup_set_free_ Set *locales;
432 _cleanup_strv_free_ char **l = NULL;
433 int r;
434
435 locales = set_new(string_hash_func, string_compare_func);
436 if (!locales)
437 return log_oom();
438
439 r = add_locales_from_archive(locales);
440 if (r < 0 && r != -ENOENT)
441 return r;
442
443 r = add_locales_from_libdir(locales);
444 if (r < 0)
445 return r;
446
447 l = set_get_strv(locales);
448 if (!l)
449 return log_oom();
450
451 strv_sort(l);
452
453 pager_open_if_enabled();
454
455 strv_print(l);
456
457 return 0;
458 }
459
460 static int set_vconsole_keymap(DBusConnection *bus, char **args, unsigned n) {
461 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
462 dbus_bool_t interactive = true, b;
463 const char *map, *toggle_map;
464
465 assert(bus);
466 assert(args);
467
468 if (n > 3) {
469 log_error("Too many arguments.");
470 return -EINVAL;
471 }
472
473 polkit_agent_open_if_enabled();
474
475 map = args[1];
476 toggle_map = n > 2 ? args[2] : "";
477 b = arg_convert;
478
479 return bus_method_call_with_reply(
480 bus,
481 "org.freedesktop.locale1",
482 "/org/freedesktop/locale1",
483 "org.freedesktop.locale1",
484 "SetVConsoleKeyboard",
485 &reply,
486 NULL,
487 DBUS_TYPE_STRING, &map,
488 DBUS_TYPE_STRING, &toggle_map,
489 DBUS_TYPE_BOOLEAN, &b,
490 DBUS_TYPE_BOOLEAN, &interactive,
491 DBUS_TYPE_INVALID);
492 }
493
494 static Set *keymaps = NULL;
495
496 static int nftw_cb(
497 const char *fpath,
498 const struct stat *sb,
499 int tflag,
500 struct FTW *ftwbuf) {
501
502 char *p, *e;
503 int r;
504
505 if (tflag != FTW_F)
506 return 0;
507
508 if (!endswith(fpath, ".map") &&
509 !endswith(fpath, ".map.gz"))
510 return 0;
511
512 p = strdup(path_get_file_name(fpath));
513 if (!p)
514 return log_oom();
515
516 e = endswith(p, ".map");
517 if (e)
518 *e = 0;
519
520 e = endswith(p, ".map.gz");
521 if (e)
522 *e = 0;
523
524 r = set_consume(keymaps, p);
525 if (r < 0 && r != -EEXIST) {
526 log_error("Can't add keymap: %s", strerror(-r));
527 return r;
528 }
529
530 return 0;
531 }
532
533 static int list_vconsole_keymaps(DBusConnection *bus, char **args, unsigned n) {
534 _cleanup_strv_free_ char **l = NULL;
535
536 keymaps = set_new(string_hash_func, string_compare_func);
537 if (!keymaps)
538 return log_oom();
539
540 nftw("/usr/share/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
541 nftw("/usr/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
542 nftw("/lib/kbd/keymaps/", nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
543
544 l = set_get_strv(keymaps);
545 if (!l) {
546 set_free_free(keymaps);
547 return log_oom();
548 }
549
550 set_free(keymaps);
551
552 if (strv_isempty(l)) {
553 log_error("Couldn't find any console keymaps.");
554 return -ENOENT;
555 }
556
557 strv_sort(l);
558
559 pager_open_if_enabled();
560
561 strv_print(l);
562
563 return 0;
564 }
565
566 static int set_x11_keymap(DBusConnection *bus, char **args, unsigned n) {
567 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
568 dbus_bool_t interactive = true, b;
569 const char *layout, *model, *variant, *options;
570
571 assert(bus);
572 assert(args);
573
574 if (n > 5) {
575 log_error("Too many arguments.");
576 return -EINVAL;
577 }
578
579 polkit_agent_open_if_enabled();
580
581 layout = args[1];
582 model = n > 2 ? args[2] : "";
583 variant = n > 3 ? args[3] : "";
584 options = n > 4 ? args[4] : "";
585 b = arg_convert;
586
587 return bus_method_call_with_reply(
588 bus,
589 "org.freedesktop.locale1",
590 "/org/freedesktop/locale1",
591 "org.freedesktop.locale1",
592 "SetX11Keyboard",
593 &reply,
594 NULL,
595 DBUS_TYPE_STRING, &layout,
596 DBUS_TYPE_STRING, &model,
597 DBUS_TYPE_STRING, &variant,
598 DBUS_TYPE_STRING, &options,
599 DBUS_TYPE_BOOLEAN, &b,
600 DBUS_TYPE_BOOLEAN, &interactive,
601 DBUS_TYPE_INVALID);
602 }
603
604 static int list_x11_keymaps(DBusConnection *bus, char **args, unsigned n) {
605 _cleanup_fclose_ FILE *f = NULL;
606 _cleanup_strv_free_ char **list = NULL;
607 char line[LINE_MAX];
608 enum {
609 NONE,
610 MODELS,
611 LAYOUTS,
612 VARIANTS,
613 OPTIONS
614 } state = NONE, look_for;
615 int r;
616
617 if (n > 2) {
618 log_error("Too many arguments.");
619 return -EINVAL;
620 }
621
622 f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
623 if (!f) {
624 log_error("Failed to open keyboard mapping list. %m");
625 return -errno;
626 }
627
628 if (streq(args[0], "list-x11-keymap-models"))
629 look_for = MODELS;
630 else if (streq(args[0], "list-x11-keymap-layouts"))
631 look_for = LAYOUTS;
632 else if (streq(args[0], "list-x11-keymap-variants"))
633 look_for = VARIANTS;
634 else if (streq(args[0], "list-x11-keymap-options"))
635 look_for = OPTIONS;
636 else
637 assert_not_reached("Wrong parameter");
638
639 FOREACH_LINE(line, f, break) {
640 char *l, *w;
641
642 l = strstrip(line);
643
644 if (isempty(l))
645 continue;
646
647 if (l[0] == '!') {
648 if (startswith(l, "! model"))
649 state = MODELS;
650 else if (startswith(l, "! layout"))
651 state = LAYOUTS;
652 else if (startswith(l, "! variant"))
653 state = VARIANTS;
654 else if (startswith(l, "! option"))
655 state = OPTIONS;
656 else
657 state = NONE;
658
659 continue;
660 }
661
662 if (state != look_for)
663 continue;
664
665 w = l + strcspn(l, WHITESPACE);
666
667 if (n > 1) {
668 char *e;
669
670 if (*w == 0)
671 continue;
672
673 *w = 0;
674 w++;
675 w += strspn(w, WHITESPACE);
676
677 e = strchr(w, ':');
678 if (!e)
679 continue;
680
681 *e = 0;
682
683 if (!streq(w, args[1]))
684 continue;
685 } else
686 *w = 0;
687
688 r = strv_extend(&list, l);
689 if (r < 0)
690 return log_oom();
691 }
692
693 if (strv_isempty(list)) {
694 log_error("Couldn't find any entries.");
695 return -ENOENT;
696 }
697
698 strv_sort(list);
699 strv_uniq(list);
700
701 pager_open_if_enabled();
702
703 strv_print(list);
704 return 0;
705 }
706
707 static int help(void) {
708
709 printf("%s [OPTIONS...] COMMAND ...\n\n"
710 "Query or change system locale and keyboard settings.\n\n"
711 " -h --help Show this help\n"
712 " --version Show package version\n"
713 " --no-convert Don't convert keyboard mappings\n"
714 " --no-pager Do not pipe output into a pager\n"
715 " --no-ask-password Do not prompt for password\n"
716 " -H --host=[USER@]HOST Operate on remote host\n\n"
717 "Commands:\n"
718 " status Show current locale settings\n"
719 " set-locale LOCALE... Set system locale\n"
720 " list-locales Show known locales\n"
721 " set-keymap MAP [MAP] Set virtual console keyboard mapping\n"
722 " list-keymaps Show known virtual console keyboard mappings\n"
723 " set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
724 " Set X11 keyboard mapping\n"
725 " list-x11-keymap-models Show known X11 keyboard mapping models\n"
726 " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n"
727 " list-x11-keymap-variants [LAYOUT]\n"
728 " Show known X11 keyboard mapping variants\n"
729 " list-x11-keymap-options Show known X11 keyboard mapping options\n",
730 program_invocation_short_name);
731
732 return 0;
733 }
734
735 static int parse_argv(int argc, char *argv[]) {
736
737 enum {
738 ARG_VERSION = 0x100,
739 ARG_NO_PAGER,
740 ARG_NO_CONVERT,
741 ARG_NO_ASK_PASSWORD
742 };
743
744 static const struct option options[] = {
745 { "help", no_argument, NULL, 'h' },
746 { "version", no_argument, NULL, ARG_VERSION },
747 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
748 { "host", required_argument, NULL, 'H' },
749 { "privileged", no_argument, NULL, 'P' },
750 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
751 { "no-convert", no_argument, NULL, ARG_NO_CONVERT },
752 { NULL, 0, NULL, 0 }
753 };
754
755 int c;
756
757 assert(argc >= 0);
758 assert(argv);
759
760 while ((c = getopt_long(argc, argv, "has:H:P", options, NULL)) >= 0) {
761
762 switch (c) {
763
764 case 'h':
765 help();
766 return 0;
767
768 case ARG_VERSION:
769 puts(PACKAGE_STRING);
770 puts(SYSTEMD_FEATURES);
771 return 0;
772
773 case 'P':
774 arg_transport = TRANSPORT_POLKIT;
775 break;
776
777 case 'H':
778 arg_transport = TRANSPORT_SSH;
779 arg_host = optarg;
780 break;
781
782 case ARG_NO_CONVERT:
783 arg_convert = false;
784 break;
785
786 case ARG_NO_PAGER:
787 arg_no_pager = true;
788 break;
789
790 case '?':
791 return -EINVAL;
792
793 default:
794 log_error("Unknown option code %c", c);
795 return -EINVAL;
796 }
797 }
798
799 return 1;
800 }
801
802 static int localectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
803
804 static const struct {
805 const char* verb;
806 const enum {
807 MORE,
808 LESS,
809 EQUAL
810 } argc_cmp;
811 const int argc;
812 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
813 } verbs[] = {
814 { "status", LESS, 1, show_status },
815 { "set-locale", MORE, 2, set_locale },
816 { "list-locales", EQUAL, 1, list_locales },
817 { "set-keymap", MORE, 2, set_vconsole_keymap },
818 { "list-keymaps", EQUAL, 1, list_vconsole_keymaps },
819 { "set-x11-keymap", MORE, 2, set_x11_keymap },
820 { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps },
821 { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps },
822 { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps },
823 { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps },
824 };
825
826 int left;
827 unsigned i;
828
829 assert(argc >= 0);
830 assert(argv);
831 assert(error);
832
833 left = argc - optind;
834
835 if (left <= 0)
836 /* Special rule: no arguments means "status" */
837 i = 0;
838 else {
839 if (streq(argv[optind], "help")) {
840 help();
841 return 0;
842 }
843
844 for (i = 0; i < ELEMENTSOF(verbs); i++)
845 if (streq(argv[optind], verbs[i].verb))
846 break;
847
848 if (i >= ELEMENTSOF(verbs)) {
849 log_error("Unknown operation %s", argv[optind]);
850 return -EINVAL;
851 }
852 }
853
854 switch (verbs[i].argc_cmp) {
855
856 case EQUAL:
857 if (left != verbs[i].argc) {
858 log_error("Invalid number of arguments.");
859 return -EINVAL;
860 }
861
862 break;
863
864 case MORE:
865 if (left < verbs[i].argc) {
866 log_error("Too few arguments.");
867 return -EINVAL;
868 }
869
870 break;
871
872 case LESS:
873 if (left > verbs[i].argc) {
874 log_error("Too many arguments.");
875 return -EINVAL;
876 }
877
878 break;
879
880 default:
881 assert_not_reached("Unknown comparison operator.");
882 }
883
884 if (!bus) {
885 log_error("Failed to get D-Bus connection: %s", error->message);
886 return -EIO;
887 }
888
889 return verbs[i].dispatch(bus, argv + optind, left);
890 }
891
892 int main(int argc, char *argv[]) {
893 int r, retval = EXIT_FAILURE;
894 DBusConnection *bus = NULL;
895 DBusError error;
896
897 dbus_error_init(&error);
898
899 setlocale(LC_ALL, "");
900 log_parse_environment();
901 log_open();
902
903 r = parse_argv(argc, argv);
904 if (r < 0)
905 goto finish;
906 else if (r == 0) {
907 retval = EXIT_SUCCESS;
908 goto finish;
909 }
910
911 if (arg_transport == TRANSPORT_NORMAL)
912 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
913 else if (arg_transport == TRANSPORT_POLKIT)
914 bus_connect_system_polkit(&bus, &error);
915 else if (arg_transport == TRANSPORT_SSH)
916 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
917 else
918 assert_not_reached("Uh, invalid transport...");
919
920 r = localectl_main(bus, argc, argv, &error);
921 retval = r < 0 ? EXIT_FAILURE : r;
922
923 finish:
924 if (bus) {
925 dbus_connection_flush(bus);
926 dbus_connection_close(bus);
927 dbus_connection_unref(bus);
928 }
929
930 dbus_error_free(&error);
931 dbus_shutdown();
932
933 pager_close();
934
935 return retval;
936 }