1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <getopt.h>
4 #include <stdbool.h>
5 #include <stdlib.h>
6 
7 #include "sd-bus.h"
8 
9 #include "bus-error.h"
10 #include "bus-locator.h"
11 #include "bus-map-properties.h"
12 #include "fd-util.h"
13 #include "fileio.h"
14 #include "kbd-util.h"
15 #include "locale-util.h"
16 #include "main-func.h"
17 #include "memory-util.h"
18 #include "pager.h"
19 #include "pretty-print.h"
20 #include "proc-cmdline.h"
21 #include "set.h"
22 #include "spawn-polkit-agent.h"
23 #include "strv.h"
24 #include "terminal-util.h"
25 #include "verbs.h"
26 #include "virt.h"
27 
28 /* Enough time for locale-gen to finish server-side (in case it is in use) */
29 #define LOCALE_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
30 
31 static PagerFlags arg_pager_flags = 0;
32 static bool arg_ask_password = true;
33 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
34 static const char *arg_host = NULL;
35 static bool arg_convert = true;
36 
37 typedef struct StatusInfo {
38         char **locale;
39         const char *vconsole_keymap;
40         const char *vconsole_keymap_toggle;
41         const char *x11_layout;
42         const char *x11_model;
43         const char *x11_variant;
44         const char *x11_options;
45 } StatusInfo;
46 
status_info_clear(StatusInfo * info)47 static void status_info_clear(StatusInfo *info) {
48         if (info) {
49                 strv_free(info->locale);
50                 zero(*info);
51         }
52 }
53 
print_overridden_variables(void)54 static void print_overridden_variables(void) {
55         _cleanup_(locale_variables_freep) char *variables[_VARIABLE_LC_MAX] = {};
56         bool print_warning = true;
57         int r;
58 
59         if (arg_transport != BUS_TRANSPORT_LOCAL)
60                 return;
61 
62         r = proc_cmdline_get_key_many(
63                         PROC_CMDLINE_STRIP_RD_PREFIX,
64                         "locale.LANG",              &variables[VARIABLE_LANG],
65                         "locale.LANGUAGE",          &variables[VARIABLE_LANGUAGE],
66                         "locale.LC_CTYPE",          &variables[VARIABLE_LC_CTYPE],
67                         "locale.LC_NUMERIC",        &variables[VARIABLE_LC_NUMERIC],
68                         "locale.LC_TIME",           &variables[VARIABLE_LC_TIME],
69                         "locale.LC_COLLATE",        &variables[VARIABLE_LC_COLLATE],
70                         "locale.LC_MONETARY",       &variables[VARIABLE_LC_MONETARY],
71                         "locale.LC_MESSAGES",       &variables[VARIABLE_LC_MESSAGES],
72                         "locale.LC_PAPER",          &variables[VARIABLE_LC_PAPER],
73                         "locale.LC_NAME",           &variables[VARIABLE_LC_NAME],
74                         "locale.LC_ADDRESS",        &variables[VARIABLE_LC_ADDRESS],
75                         "locale.LC_TELEPHONE",      &variables[VARIABLE_LC_TELEPHONE],
76                         "locale.LC_MEASUREMENT",    &variables[VARIABLE_LC_MEASUREMENT],
77                         "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION]);
78         if (r < 0 && r != -ENOENT) {
79                 log_warning_errno(r, "Failed to read /proc/cmdline: %m");
80                 return;
81         }
82 
83         for (LocaleVariable j = 0; j < _VARIABLE_LC_MAX; j++)
84                 if (variables[j]) {
85                         if (print_warning) {
86                                 log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n"
87                                             "    Command Line: %s=%s", locale_variable_to_string(j), variables[j]);
88 
89                                 print_warning = false;
90                         } else
91                                 log_warning("                  %s=%s", locale_variable_to_string(j), variables[j]);
92                 }
93 }
94 
print_status_info(StatusInfo * i)95 static void print_status_info(StatusInfo *i) {
96         assert(i);
97 
98         if (strv_isempty(i->locale))
99                 puts("   System Locale: n/a");
100         else {
101                 printf("   System Locale: %s\n", i->locale[0]);
102                 STRV_FOREACH(j, i->locale + 1)
103                         printf("                  %s\n", *j);
104         }
105 
106         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
107         if (!isempty(i->vconsole_keymap_toggle))
108                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
109 
110         printf("      X11 Layout: %s\n", strna(i->x11_layout));
111         if (!isempty(i->x11_model))
112                 printf("       X11 Model: %s\n", i->x11_model);
113         if (!isempty(i->x11_variant))
114                 printf("     X11 Variant: %s\n", i->x11_variant);
115         if (!isempty(i->x11_options))
116                 printf("     X11 Options: %s\n", i->x11_options);
117 }
118 
show_status(int argc,char ** argv,void * userdata)119 static int show_status(int argc, char **argv, void *userdata) {
120         _cleanup_(status_info_clear) StatusInfo info = {};
121         static const struct bus_properties_map map[]  = {
122                 { "VConsoleKeymap",       "s",  NULL, offsetof(StatusInfo, vconsole_keymap) },
123                 { "VConsoleKeymapToggle", "s",  NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
124                 { "X11Layout",            "s",  NULL, offsetof(StatusInfo, x11_layout) },
125                 { "X11Model",             "s",  NULL, offsetof(StatusInfo, x11_model) },
126                 { "X11Variant",           "s",  NULL, offsetof(StatusInfo, x11_variant) },
127                 { "X11Options",           "s",  NULL, offsetof(StatusInfo, x11_options) },
128                 { "Locale",               "as", NULL, offsetof(StatusInfo, locale) },
129                 {}
130         };
131 
132         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
133         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
134         sd_bus *bus = userdata;
135         int r;
136 
137         assert(bus);
138 
139         r = bus_map_all_properties(bus,
140                                    "org.freedesktop.locale1",
141                                    "/org/freedesktop/locale1",
142                                    map,
143                                    0,
144                                    &error,
145                                    &m,
146                                    &info);
147         if (r < 0)
148                 return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
149 
150         print_overridden_variables();
151         print_status_info(&info);
152 
153         return r;
154 }
155 
set_locale(int argc,char ** argv,void * userdata)156 static int set_locale(int argc, char **argv, void *userdata) {
157         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
158         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
159         sd_bus *bus = userdata;
160         int r;
161 
162         assert(bus);
163 
164         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
165 
166         r = bus_message_new_method_call(bus, &m, bus_locale, "SetLocale");
167         if (r < 0)
168                 return bus_log_create_error(r);
169 
170         r = sd_bus_message_append_strv(m, argv + 1);
171         if (r < 0)
172                 return bus_log_create_error(r);
173 
174         r = sd_bus_message_append(m, "b", arg_ask_password);
175         if (r < 0)
176                 return bus_log_create_error(r);
177 
178         /* We use a longer timeout for the method call in case localed is running locale-gen */
179         r = sd_bus_call(bus, m, LOCALE_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
180         if (r < 0)
181                 return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
182 
183         return 0;
184 }
185 
list_locales(int argc,char ** argv,void * userdata)186 static int list_locales(int argc, char **argv, void *userdata) {
187         _cleanup_strv_free_ char **l = NULL;
188         int r;
189 
190         r = get_locales(&l);
191         if (r < 0)
192                 return log_error_errno(r, "Failed to read list of locales: %m");
193 
194         pager_open(arg_pager_flags);
195         strv_print(l);
196 
197         return 0;
198 }
199 
set_vconsole_keymap(int argc,char ** argv,void * userdata)200 static int set_vconsole_keymap(int argc, char **argv, void *userdata) {
201         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
202         const char *map, *toggle_map;
203         sd_bus *bus = userdata;
204         int r;
205 
206         assert(bus);
207 
208         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
209 
210         map = argv[1];
211         toggle_map = argc > 2 ? argv[2] : "";
212 
213         r = bus_call_method(
214                         bus,
215                         bus_locale,
216                         "SetVConsoleKeyboard",
217                         &error,
218                         NULL,
219                         "ssbb", map, toggle_map, arg_convert, arg_ask_password);
220         if (r < 0)
221                 return log_error_errno(r, "Failed to set keymap: %s", bus_error_message(&error, r));
222 
223         return 0;
224 }
225 
list_vconsole_keymaps(int argc,char ** argv,void * userdata)226 static int list_vconsole_keymaps(int argc, char **argv, void *userdata) {
227         _cleanup_strv_free_ char **l = NULL;
228         int r;
229 
230         r = get_keymaps(&l);
231         if (r < 0)
232                 return log_error_errno(r, "Failed to read list of keymaps: %m");
233 
234         pager_open(arg_pager_flags);
235 
236         strv_print(l);
237 
238         return 0;
239 }
240 
set_x11_keymap(int argc,char ** argv,void * userdata)241 static int set_x11_keymap(int argc, char **argv, void *userdata) {
242         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
243         const char *layout, *model, *variant, *options;
244         sd_bus *bus = userdata;
245         int r;
246 
247         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
248 
249         layout = argv[1];
250         model = argc > 2 ? argv[2] : "";
251         variant = argc > 3 ? argv[3] : "";
252         options = argc > 4 ? argv[4] : "";
253 
254         r = bus_call_method(
255                         bus,
256                         bus_locale,
257                         "SetX11Keyboard",
258                         &error,
259                         NULL,
260                         "ssssbb", layout, model, variant, options,
261                                   arg_convert, arg_ask_password);
262         if (r < 0)
263                 return log_error_errno(r, "Failed to set keymap: %s", bus_error_message(&error, r));
264 
265         return 0;
266 }
267 
list_x11_keymaps(int argc,char ** argv,void * userdata)268 static int list_x11_keymaps(int argc, char **argv, void *userdata) {
269         _cleanup_fclose_ FILE *f = NULL;
270         _cleanup_strv_free_ char **list = NULL;
271         enum {
272                 NONE,
273                 MODELS,
274                 LAYOUTS,
275                 VARIANTS,
276                 OPTIONS
277         } state = NONE, look_for;
278         int r;
279 
280         f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
281         if (!f)
282                 return log_error_errno(errno, "Failed to open keyboard mapping list. %m");
283 
284         if (streq(argv[0], "list-x11-keymap-models"))
285                 look_for = MODELS;
286         else if (streq(argv[0], "list-x11-keymap-layouts"))
287                 look_for = LAYOUTS;
288         else if (streq(argv[0], "list-x11-keymap-variants"))
289                 look_for = VARIANTS;
290         else if (streq(argv[0], "list-x11-keymap-options"))
291                 look_for = OPTIONS;
292         else
293                 assert_not_reached();
294 
295         for (;;) {
296                 _cleanup_free_ char *line = NULL;
297                 char *l, *w;
298 
299                 r = read_line(f, LONG_LINE_MAX, &line);
300                 if (r < 0)
301                         return log_error_errno(r, "Failed to read keyboard mapping list: %m");
302                 if (r == 0)
303                         break;
304 
305                 l = strstrip(line);
306 
307                 if (isempty(l))
308                         continue;
309 
310                 if (l[0] == '!') {
311                         if (startswith(l, "! model"))
312                                 state = MODELS;
313                         else if (startswith(l, "! layout"))
314                                 state = LAYOUTS;
315                         else if (startswith(l, "! variant"))
316                                 state = VARIANTS;
317                         else if (startswith(l, "! option"))
318                                 state = OPTIONS;
319                         else
320                                 state = NONE;
321 
322                         continue;
323                 }
324 
325                 if (state != look_for)
326                         continue;
327 
328                 w = l + strcspn(l, WHITESPACE);
329 
330                 if (argc > 1) {
331                         char *e;
332 
333                         if (*w == 0)
334                                 continue;
335 
336                         *w = 0;
337                         w++;
338                         w += strspn(w, WHITESPACE);
339 
340                         e = strchr(w, ':');
341                         if (!e)
342                                 continue;
343 
344                         *e = 0;
345 
346                         if (!streq(w, argv[1]))
347                                 continue;
348                 } else
349                         *w = 0;
350 
351                 r = strv_extend(&list, l);
352                 if (r < 0)
353                         return log_oom();
354         }
355 
356         if (strv_isempty(list))
357                 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
358                                        "Couldn't find any entries.");
359 
360         strv_sort(list);
361         strv_uniq(list);
362 
363         pager_open(arg_pager_flags);
364 
365         strv_print(list);
366         return 0;
367 }
368 
help(void)369 static int help(void) {
370         _cleanup_free_ char *link = NULL;
371         int r;
372 
373         r = terminal_urlify_man("localectl", "1", &link);
374         if (r < 0)
375                 return log_oom();
376 
377         printf("%s [OPTIONS...] COMMAND ...\n\n"
378                "%sQuery or change system locale and keyboard settings.%s\n"
379                "\nCommands:\n"
380                "  status                   Show current locale settings\n"
381                "  set-locale LOCALE...     Set system locale\n"
382                "  list-locales             Show known locales\n"
383                "  set-keymap MAP [MAP]     Set console and X11 keyboard mappings\n"
384                "  list-keymaps             Show known virtual console keyboard mappings\n"
385                "  set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n"
386                "                           Set X11 and console keyboard mappings\n"
387                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
388                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
389                "  list-x11-keymap-variants [LAYOUT]\n"
390                "                           Show known X11 keyboard mapping variants\n"
391                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n"
392                "\nOptions:\n"
393                "  -h --help                Show this help\n"
394                "     --version             Show package version\n"
395                "     --no-pager            Do not pipe output into a pager\n"
396                "     --no-ask-password     Do not prompt for password\n"
397                "  -H --host=[USER@]HOST    Operate on remote host\n"
398                "  -M --machine=CONTAINER   Operate on local container\n"
399                "     --no-convert          Don't convert keyboard mappings\n"
400                "\nSee the %s for details.\n",
401                program_invocation_short_name,
402                ansi_highlight(),
403                ansi_normal(),
404                link);
405 
406         return 0;
407 }
408 
verb_help(int argc,char ** argv,void * userdata)409 static int verb_help(int argc, char **argv, void *userdata) {
410         return help();
411 }
412 
parse_argv(int argc,char * argv[])413 static int parse_argv(int argc, char *argv[]) {
414 
415         enum {
416                 ARG_VERSION = 0x100,
417                 ARG_NO_PAGER,
418                 ARG_NO_CONVERT,
419                 ARG_NO_ASK_PASSWORD
420         };
421 
422         static const struct option options[] = {
423                 { "help",            no_argument,       NULL, 'h'                 },
424                 { "version",         no_argument,       NULL, ARG_VERSION         },
425                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
426                 { "host",            required_argument, NULL, 'H'                 },
427                 { "machine",         required_argument, NULL, 'M'                 },
428                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
429                 { "no-convert",      no_argument,       NULL, ARG_NO_CONVERT      },
430                 {}
431         };
432 
433         int c;
434 
435         assert(argc >= 0);
436         assert(argv);
437 
438         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
439 
440                 switch (c) {
441 
442                 case 'h':
443                         return help();
444 
445                 case ARG_VERSION:
446                         return version();
447 
448                 case ARG_NO_CONVERT:
449                         arg_convert = false;
450                         break;
451 
452                 case ARG_NO_PAGER:
453                         arg_pager_flags |= PAGER_DISABLE;
454                         break;
455 
456                 case ARG_NO_ASK_PASSWORD:
457                         arg_ask_password = false;
458                         break;
459 
460                 case 'H':
461                         arg_transport = BUS_TRANSPORT_REMOTE;
462                         arg_host = optarg;
463                         break;
464 
465                 case 'M':
466                         arg_transport = BUS_TRANSPORT_MACHINE;
467                         arg_host = optarg;
468                         break;
469 
470                 case '?':
471                         return -EINVAL;
472 
473                 default:
474                         assert_not_reached();
475                 }
476 
477         return 1;
478 }
479 
localectl_main(sd_bus * bus,int argc,char * argv[])480 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
481 
482         static const Verb verbs[] = {
483                 { "status",                   VERB_ANY, 1,        VERB_DEFAULT, show_status           },
484                 { "set-locale",               2,        VERB_ANY, 0,            set_locale            },
485                 { "list-locales",             VERB_ANY, 1,        0,            list_locales          },
486                 { "set-keymap",               2,        3,        0,            set_vconsole_keymap   },
487                 { "list-keymaps",             VERB_ANY, 1,        0,            list_vconsole_keymaps },
488                 { "set-x11-keymap",           2,        5,        0,            set_x11_keymap        },
489                 { "list-x11-keymap-models",   VERB_ANY, 1,        0,            list_x11_keymaps      },
490                 { "list-x11-keymap-layouts",  VERB_ANY, 1,        0,            list_x11_keymaps      },
491                 { "list-x11-keymap-variants", VERB_ANY, 2,        0,            list_x11_keymaps      },
492                 { "list-x11-keymap-options",  VERB_ANY, 1,        0,            list_x11_keymaps      },
493                 { "help",                     VERB_ANY, VERB_ANY, 0,            verb_help             }, /* Not documented, but supported since it is created. */
494                 {}
495         };
496 
497         return dispatch_verb(argc, argv, verbs, bus);
498 }
499 
run(int argc,char * argv[])500 static int run(int argc, char *argv[]) {
501         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
502         int r;
503 
504         setlocale(LC_ALL, "");
505         log_setup();
506 
507         r = parse_argv(argc, argv);
508         if (r <= 0)
509                 return r;
510 
511         r = bus_connect_transport(arg_transport, arg_host, false, &bus);
512         if (r < 0)
513                 return bus_log_connect_error(r, arg_transport);
514 
515         return localectl_main(bus, argc, argv);
516 }
517 
518 DEFINE_MAIN_FUNCTION(run);
519