]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
Merge pull request #4061 from dm0-/coreos-1545
[thirdparty/systemd.git] / src / locale / localed.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2011 Lennart Poettering
5 Copyright 2013 Kay Sievers
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #ifdef HAVE_XKBCOMMON
26 #include <xkbcommon/xkbcommon.h>
27 #include <dlfcn.h>
28 #endif
29
30 #include "sd-bus.h"
31
32 #include "alloc-util.h"
33 #include "bus-error.h"
34 #include "bus-message.h"
35 #include "bus-util.h"
36 #include "def.h"
37 #include "keymap-util.h"
38 #include "locale-util.h"
39 #include "macro.h"
40 #include "path-util.h"
41 #include "selinux-util.h"
42 #include "string-util.h"
43 #include "strv.h"
44 #include "user-util.h"
45
46 static Hashmap *polkit_registry = NULL;
47
48 static int locale_update_system_manager(Context *c, sd_bus *bus) {
49 _cleanup_free_ char **l_unset = NULL;
50 _cleanup_strv_free_ char **l_set = NULL;
51 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
52 sd_bus_error error = SD_BUS_ERROR_NULL;
53 unsigned c_set, c_unset, p;
54 int r;
55
56 assert(bus);
57
58 l_unset = new0(char*, _VARIABLE_LC_MAX);
59 if (!l_unset)
60 return -ENOMEM;
61
62 l_set = new0(char*, _VARIABLE_LC_MAX);
63 if (!l_set)
64 return -ENOMEM;
65
66 for (p = 0, c_set = 0, c_unset = 0; p < _VARIABLE_LC_MAX; p++) {
67 const char *name;
68
69 name = locale_variable_to_string(p);
70 assert(name);
71
72 if (isempty(c->locale[p]))
73 l_unset[c_set++] = (char*) name;
74 else {
75 char *s;
76
77 if (asprintf(&s, "%s=%s", name, c->locale[p]) < 0)
78 return -ENOMEM;
79
80 l_set[c_unset++] = s;
81 }
82 }
83
84 assert(c_set + c_unset == _VARIABLE_LC_MAX);
85 r = sd_bus_message_new_method_call(bus, &m,
86 "org.freedesktop.systemd1",
87 "/org/freedesktop/systemd1",
88 "org.freedesktop.systemd1.Manager",
89 "UnsetAndSetEnvironment");
90 if (r < 0)
91 return r;
92
93 r = sd_bus_message_append_strv(m, l_unset);
94 if (r < 0)
95 return r;
96
97 r = sd_bus_message_append_strv(m, l_set);
98 if (r < 0)
99 return r;
100
101 r = sd_bus_call(bus, m, 0, &error, NULL);
102 if (r < 0)
103 log_error_errno(r, "Failed to update the manager environment: %m");
104
105 return 0;
106 }
107
108 static int vconsole_reload(sd_bus *bus) {
109 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
110 int r;
111
112 assert(bus);
113
114 r = sd_bus_call_method(bus,
115 "org.freedesktop.systemd1",
116 "/org/freedesktop/systemd1",
117 "org.freedesktop.systemd1.Manager",
118 "RestartUnit",
119 &error,
120 NULL,
121 "ss", "systemd-vconsole-setup.service", "replace");
122
123 if (r < 0)
124 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
125 return r;
126 }
127
128 static int vconsole_convert_to_x11_and_emit(Context *c, sd_bus *bus) {
129 int r;
130
131 assert(bus);
132
133 r = vconsole_convert_to_x11(c);
134 if (r <= 0)
135 return r;
136
137 /* modified */
138 r = x11_write_data(c);
139 if (r < 0)
140 return log_error_errno(r, "Failed to write X11 keyboard layout: %m");
141
142 sd_bus_emit_properties_changed(bus,
143 "/org/freedesktop/locale1",
144 "org.freedesktop.locale1",
145 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
146
147 return 1;
148 }
149
150 static int x11_convert_to_vconsole_and_emit(Context *c, sd_bus *bus) {
151 int r;
152
153 assert(bus);
154
155 r = x11_convert_to_vconsole(c);
156 if (r <= 0)
157 return r;
158
159 /* modified */
160 r = vconsole_write_data(c);
161 if (r < 0)
162 log_error_errno(r, "Failed to save virtual console keymap: %m");
163
164 sd_bus_emit_properties_changed(bus,
165 "/org/freedesktop/locale1",
166 "org.freedesktop.locale1",
167 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
168
169 return vconsole_reload(bus);
170 }
171
172 static int property_get_locale(
173 sd_bus *bus,
174 const char *path,
175 const char *interface,
176 const char *property,
177 sd_bus_message *reply,
178 void *userdata,
179 sd_bus_error *error) {
180
181 Context *c = userdata;
182 _cleanup_strv_free_ char **l = NULL;
183 int p, q;
184
185 l = new0(char*, _VARIABLE_LC_MAX+1);
186 if (!l)
187 return -ENOMEM;
188
189 for (p = 0, q = 0; p < _VARIABLE_LC_MAX; p++) {
190 char *t;
191 const char *name;
192
193 name = locale_variable_to_string(p);
194 assert(name);
195
196 if (isempty(c->locale[p]))
197 continue;
198
199 if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0)
200 return -ENOMEM;
201
202 l[q++] = t;
203 }
204
205 return sd_bus_message_append_strv(reply, l);
206 }
207
208 static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) {
209 Context *c = userdata;
210 _cleanup_strv_free_ char **l = NULL;
211 char **i;
212 const char *lang = NULL;
213 int interactive;
214 bool modified = false;
215 bool have[_VARIABLE_LC_MAX] = {};
216 int p;
217 int r;
218
219 assert(m);
220 assert(c);
221
222 r = bus_message_read_strv_extend(m, &l);
223 if (r < 0)
224 return r;
225
226 r = sd_bus_message_read_basic(m, 'b', &interactive);
227 if (r < 0)
228 return r;
229
230 /* Check whether a variable changed and if it is valid */
231 STRV_FOREACH(i, l) {
232 bool valid = false;
233
234 for (p = 0; p < _VARIABLE_LC_MAX; p++) {
235 size_t k;
236 const char *name;
237
238 name = locale_variable_to_string(p);
239 assert(name);
240
241 k = strlen(name);
242 if (startswith(*i, name) &&
243 (*i)[k] == '=' &&
244 locale_is_valid((*i) + k + 1)) {
245 valid = true;
246 have[p] = true;
247
248 if (p == VARIABLE_LANG)
249 lang = (*i) + k + 1;
250
251 if (!streq_ptr(*i + k + 1, c->locale[p]))
252 modified = true;
253
254 break;
255 }
256 }
257
258 if (!valid)
259 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
260 }
261
262 /* If LANG was specified, but not LANGUAGE, check if we should
263 * set it based on the language fallback table. */
264 if (have[VARIABLE_LANG] && !have[VARIABLE_LANGUAGE]) {
265 _cleanup_free_ char *language = NULL;
266
267 assert(lang);
268
269 (void) find_language_fallback(lang, &language);
270 if (language) {
271 log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
272 if (!streq_ptr(language, c->locale[VARIABLE_LANGUAGE])) {
273 r = strv_extendf(&l, "LANGUAGE=%s", language);
274 if (r < 0)
275 return r;
276
277 have[VARIABLE_LANGUAGE] = true;
278 modified = true;
279 }
280 }
281 }
282
283 /* Check whether a variable is unset */
284 if (!modified)
285 for (p = 0; p < _VARIABLE_LC_MAX; p++)
286 if (!isempty(c->locale[p]) && !have[p]) {
287 modified = true;
288 break;
289 }
290
291 if (modified) {
292 _cleanup_strv_free_ char **settings = NULL;
293
294 r = bus_verify_polkit_async(
295 m,
296 CAP_SYS_ADMIN,
297 "org.freedesktop.locale1.set-locale",
298 NULL,
299 interactive,
300 UID_INVALID,
301 &polkit_registry,
302 error);
303 if (r < 0)
304 return r;
305 if (r == 0)
306 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
307
308 STRV_FOREACH(i, l)
309 for (p = 0; p < _VARIABLE_LC_MAX; p++) {
310 size_t k;
311 const char *name;
312
313 name = locale_variable_to_string(p);
314 assert(name);
315
316 k = strlen(name);
317 if (startswith(*i, name) && (*i)[k] == '=') {
318 r = free_and_strdup(&c->locale[p], *i + k + 1);
319 if (r < 0)
320 return r;
321 break;
322 }
323 }
324
325 for (p = 0; p < _VARIABLE_LC_MAX; p++) {
326 if (have[p])
327 continue;
328
329 c->locale[p] = mfree(c->locale[p]);
330 }
331
332 locale_simplify(c);
333
334 r = locale_write_data(c, &settings);
335 if (r < 0) {
336 log_error_errno(r, "Failed to set locale: %m");
337 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %m");
338 }
339
340 locale_update_system_manager(c, sd_bus_message_get_bus(m));
341
342 if (settings) {
343 _cleanup_free_ char *line;
344
345 line = strv_join(settings, ", ");
346 log_info("Changed locale to %s.", strnull(line));
347 } else
348 log_info("Changed locale to unset.");
349
350 (void) sd_bus_emit_properties_changed(
351 sd_bus_message_get_bus(m),
352 "/org/freedesktop/locale1",
353 "org.freedesktop.locale1",
354 "Locale", NULL);
355 } else
356 log_debug("Locale settings were not modified.");
357
358
359 return sd_bus_reply_method_return(m, NULL);
360 }
361
362 static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
363 Context *c = userdata;
364 const char *keymap, *keymap_toggle;
365 int convert, interactive;
366 int r;
367
368 assert(m);
369 assert(c);
370
371 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
372 if (r < 0)
373 return r;
374
375 keymap = empty_to_null(keymap);
376 keymap_toggle = empty_to_null(keymap_toggle);
377
378 if (!streq_ptr(keymap, c->vc_keymap) ||
379 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
380
381 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
382 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
383 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
384
385 r = bus_verify_polkit_async(
386 m,
387 CAP_SYS_ADMIN,
388 "org.freedesktop.locale1.set-keyboard",
389 NULL,
390 interactive,
391 UID_INVALID,
392 &polkit_registry,
393 error);
394 if (r < 0)
395 return r;
396 if (r == 0)
397 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
398
399 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
400 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
401 return -ENOMEM;
402
403 r = vconsole_write_data(c);
404 if (r < 0) {
405 log_error_errno(r, "Failed to set virtual console keymap: %m");
406 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %m");
407 }
408
409 log_info("Changed virtual console keymap to '%s' toggle '%s'",
410 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
411
412 r = vconsole_reload(sd_bus_message_get_bus(m));
413 if (r < 0)
414 log_error_errno(r, "Failed to request keymap reload: %m");
415
416 (void) sd_bus_emit_properties_changed(
417 sd_bus_message_get_bus(m),
418 "/org/freedesktop/locale1",
419 "org.freedesktop.locale1",
420 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
421
422 if (convert) {
423 r = vconsole_convert_to_x11_and_emit(c, sd_bus_message_get_bus(m));
424 if (r < 0)
425 log_error_errno(r, "Failed to convert keymap data: %m");
426 }
427 }
428
429 return sd_bus_reply_method_return(m, NULL);
430 }
431
432 #ifdef HAVE_XKBCOMMON
433
434 _printf_(3, 0)
435 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
436 const char *fmt;
437
438 fmt = strjoina("libxkbcommon: ", format);
439 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
440 }
441
442 #define LOAD_SYMBOL(symbol, dl, name) \
443 ({ \
444 (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
445 (symbol) ? 0 : -EOPNOTSUPP; \
446 })
447
448 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
449
450 /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
451 * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
452 * pointers to the shared library are below: */
453
454 struct xkb_context* (*symbol_xkb_context_new)(enum xkb_context_flags flags) = NULL;
455 void (*symbol_xkb_context_unref)(struct xkb_context *context) = NULL;
456 void (*symbol_xkb_context_set_log_fn)(struct xkb_context *context, void (*log_fn)(struct xkb_context *context, enum xkb_log_level level, const char *format, va_list args)) = NULL;
457 struct xkb_keymap* (*symbol_xkb_keymap_new_from_names)(struct xkb_context *context, const struct xkb_rule_names *names, enum xkb_keymap_compile_flags flags) = NULL;
458 void (*symbol_xkb_keymap_unref)(struct xkb_keymap *keymap) = NULL;
459
460 const struct xkb_rule_names rmlvo = {
461 .model = model,
462 .layout = layout,
463 .variant = variant,
464 .options = options,
465 };
466 struct xkb_context *ctx = NULL;
467 struct xkb_keymap *km = NULL;
468 void *dl;
469 int r;
470
471 /* Compile keymap from RMLVO information to check out its validity */
472
473 dl = dlopen("libxkbcommon.so.0", RTLD_LAZY);
474 if (!dl)
475 return -EOPNOTSUPP;
476
477 r = LOAD_SYMBOL(symbol_xkb_context_new, dl, "xkb_context_new");
478 if (r < 0)
479 goto finish;
480
481 r = LOAD_SYMBOL(symbol_xkb_context_unref, dl, "xkb_context_unref");
482 if (r < 0)
483 goto finish;
484
485 r = LOAD_SYMBOL(symbol_xkb_context_set_log_fn, dl, "xkb_context_set_log_fn");
486 if (r < 0)
487 goto finish;
488
489 r = LOAD_SYMBOL(symbol_xkb_keymap_new_from_names, dl, "xkb_keymap_new_from_names");
490 if (r < 0)
491 goto finish;
492
493 r = LOAD_SYMBOL(symbol_xkb_keymap_unref, dl, "xkb_keymap_unref");
494 if (r < 0)
495 goto finish;
496
497 ctx = symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
498 if (!ctx) {
499 r = -ENOMEM;
500 goto finish;
501 }
502
503 symbol_xkb_context_set_log_fn(ctx, log_xkb);
504
505 km = symbol_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
506 if (!km) {
507 r = -EINVAL;
508 goto finish;
509 }
510
511 r = 0;
512
513 finish:
514 if (symbol_xkb_keymap_unref && km)
515 symbol_xkb_keymap_unref(km);
516
517 if (symbol_xkb_context_unref && ctx)
518 symbol_xkb_context_unref(ctx);
519
520 (void) dlclose(dl);
521 return r;
522 }
523
524 #else
525
526 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
527 return 0;
528 }
529
530 #endif
531
532 static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
533 Context *c = userdata;
534 const char *layout, *model, *variant, *options;
535 int convert, interactive;
536 int r;
537
538 assert(m);
539 assert(c);
540
541 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
542 if (r < 0)
543 return r;
544
545 layout = empty_to_null(layout);
546 model = empty_to_null(model);
547 variant = empty_to_null(variant);
548 options = empty_to_null(options);
549
550 if (!streq_ptr(layout, c->x11_layout) ||
551 !streq_ptr(model, c->x11_model) ||
552 !streq_ptr(variant, c->x11_variant) ||
553 !streq_ptr(options, c->x11_options)) {
554
555 if ((layout && !string_is_safe(layout)) ||
556 (model && !string_is_safe(model)) ||
557 (variant && !string_is_safe(variant)) ||
558 (options && !string_is_safe(options)))
559 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
560
561 r = bus_verify_polkit_async(
562 m,
563 CAP_SYS_ADMIN,
564 "org.freedesktop.locale1.set-keyboard",
565 NULL,
566 interactive,
567 UID_INVALID,
568 &polkit_registry,
569 error);
570 if (r < 0)
571 return r;
572 if (r == 0)
573 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
574
575 r = verify_xkb_rmlvo(model, layout, variant, options);
576 if (r < 0) {
577 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
578 strempty(model), strempty(layout), strempty(variant), strempty(options));
579
580 if (r == -EOPNOTSUPP)
581 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Local keyboard configuration not supported on this system.");
582
583 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Specified keymap cannot be compiled, refusing as invalid.");
584 }
585
586 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
587 free_and_strdup(&c->x11_model, model) < 0 ||
588 free_and_strdup(&c->x11_variant, variant) < 0 ||
589 free_and_strdup(&c->x11_options, options) < 0)
590 return -ENOMEM;
591
592 r = x11_write_data(c);
593 if (r < 0) {
594 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
595 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %m");
596 }
597
598 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
599 strempty(c->x11_layout),
600 strempty(c->x11_model),
601 strempty(c->x11_variant),
602 strempty(c->x11_options));
603
604 (void) sd_bus_emit_properties_changed(
605 sd_bus_message_get_bus(m),
606 "/org/freedesktop/locale1",
607 "org.freedesktop.locale1",
608 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
609
610 if (convert) {
611 r = x11_convert_to_vconsole_and_emit(c, sd_bus_message_get_bus(m));
612 if (r < 0)
613 log_error_errno(r, "Failed to convert keymap data: %m");
614 }
615 }
616
617 return sd_bus_reply_method_return(m, NULL);
618 }
619
620 static const sd_bus_vtable locale_vtable[] = {
621 SD_BUS_VTABLE_START(0),
622 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
623 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
624 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
625 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
626 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
627 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
628 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
629 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
630 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
631 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
632 SD_BUS_VTABLE_END
633 };
634
635 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
636 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
637 int r;
638
639 assert(c);
640 assert(event);
641 assert(_bus);
642
643 r = sd_bus_default_system(&bus);
644 if (r < 0)
645 return log_error_errno(r, "Failed to get system bus connection: %m");
646
647 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
648 if (r < 0)
649 return log_error_errno(r, "Failed to register object: %m");
650
651 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
652 if (r < 0)
653 return log_error_errno(r, "Failed to register name: %m");
654
655 r = sd_bus_attach_event(bus, event, 0);
656 if (r < 0)
657 return log_error_errno(r, "Failed to attach bus to event loop: %m");
658
659 *_bus = bus;
660 bus = NULL;
661
662 return 0;
663 }
664
665 int main(int argc, char *argv[]) {
666 _cleanup_(context_free) Context context = {};
667 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
668 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
669 int r;
670
671 log_set_target(LOG_TARGET_AUTO);
672 log_parse_environment();
673 log_open();
674
675 umask(0022);
676 mac_selinux_init();
677
678 if (argc != 1) {
679 log_error("This program takes no arguments.");
680 r = -EINVAL;
681 goto finish;
682 }
683
684 r = sd_event_default(&event);
685 if (r < 0) {
686 log_error_errno(r, "Failed to allocate event loop: %m");
687 goto finish;
688 }
689
690 sd_event_set_watchdog(event, true);
691
692 r = connect_bus(&context, event, &bus);
693 if (r < 0)
694 goto finish;
695
696 r = context_read_data(&context);
697 if (r < 0) {
698 log_error_errno(r, "Failed to read locale data: %m");
699 goto finish;
700 }
701
702 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
703 if (r < 0)
704 log_error_errno(r, "Failed to run event loop: %m");
705
706 finish:
707 bus_verify_polkit_async_registry_free(polkit_registry);
708
709 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
710 }