]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
1822350d | 2 | |
1822350d | 3 | #include <errno.h> |
ca78ad1d ZJS |
4 | #include <sys/stat.h> |
5 | #include <sys/types.h> | |
1822350d KS |
6 | #include <unistd.h> |
7 | ||
349cc4a5 | 8 | #if HAVE_XKBCOMMON |
3ffd4af2 | 9 | #include <xkbcommon/xkbcommon.h> |
5de34470 | 10 | #include <dlfcn.h> |
3ffd4af2 LP |
11 | #endif |
12 | ||
8d451309 KS |
13 | #include "sd-bus.h" |
14 | ||
b5efdb8a | 15 | #include "alloc-util.h" |
8d451309 KS |
16 | #include "bus-error.h" |
17 | #include "bus-message.h" | |
bb15fafe LP |
18 | #include "bus-util.h" |
19 | #include "def.h" | |
4897d1dc | 20 | #include "keymap-util.h" |
75683450 | 21 | #include "locale-util.h" |
4897d1dc | 22 | #include "macro.h" |
c6f09e6a | 23 | #include "main-func.h" |
36dd5ffd | 24 | #include "missing_capability.h" |
bb15fafe | 25 | #include "path-util.h" |
d7b8eec7 | 26 | #include "selinux-util.h" |
50008ae4 | 27 | #include "signal-util.h" |
4897d1dc | 28 | #include "string-util.h" |
bb15fafe | 29 | #include "strv.h" |
ee104e11 | 30 | #include "user-util.h" |
d4f5a1f4 | 31 | |
8d451309 KS |
32 | static int locale_update_system_manager(Context *c, sd_bus *bus) { |
33 | _cleanup_free_ char **l_unset = NULL; | |
34 | _cleanup_strv_free_ char **l_set = NULL; | |
4afd3348 | 35 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |
7e284b05 | 36 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
608eea8e LP |
37 | size_t c_set, c_unset; |
38 | LocaleVariable p; | |
8d451309 | 39 | int r; |
1822350d KS |
40 | |
41 | assert(bus); | |
42 | ||
78c97cbe | 43 | l_unset = new0(char*, _VARIABLE_LC_MAX); |
8d451309 | 44 | if (!l_unset) |
46e8b947 | 45 | return log_oom(); |
1822350d | 46 | |
78c97cbe | 47 | l_set = new0(char*, _VARIABLE_LC_MAX); |
8d451309 | 48 | if (!l_set) |
46e8b947 | 49 | return log_oom(); |
8d451309 | 50 | |
78c97cbe ZJS |
51 | for (p = 0, c_set = 0, c_unset = 0; p < _VARIABLE_LC_MAX; p++) { |
52 | const char *name; | |
53 | ||
54 | name = locale_variable_to_string(p); | |
55 | assert(name); | |
1822350d | 56 | |
8d451309 | 57 | if (isempty(c->locale[p])) |
78c97cbe | 58 | l_unset[c_set++] = (char*) name; |
1822350d KS |
59 | else { |
60 | char *s; | |
61 | ||
46e8b947 LP |
62 | s = strjoin(name, "=", c->locale[p]); |
63 | if (!s) | |
64 | return log_oom(); | |
1822350d KS |
65 | |
66 | l_set[c_unset++] = s; | |
67 | } | |
68 | } | |
69 | ||
78c97cbe | 70 | assert(c_set + c_unset == _VARIABLE_LC_MAX); |
151b9b96 | 71 | r = sd_bus_message_new_method_call(bus, &m, |
8d451309 KS |
72 | "org.freedesktop.systemd1", |
73 | "/org/freedesktop/systemd1", | |
74 | "org.freedesktop.systemd1.Manager", | |
151b9b96 | 75 | "UnsetAndSetEnvironment"); |
8d451309 | 76 | if (r < 0) |
46e8b947 | 77 | return bus_log_create_error(r); |
1822350d | 78 | |
8d451309 KS |
79 | r = sd_bus_message_append_strv(m, l_unset); |
80 | if (r < 0) | |
46e8b947 | 81 | return bus_log_create_error(r); |
1822350d | 82 | |
8d451309 KS |
83 | r = sd_bus_message_append_strv(m, l_set); |
84 | if (r < 0) | |
46e8b947 | 85 | return bus_log_create_error(r); |
1822350d | 86 | |
c49b30a2 | 87 | r = sd_bus_call(bus, m, 0, &error, NULL); |
8d451309 | 88 | if (r < 0) |
936d1136 | 89 | return log_error_errno(r, "Failed to update the manager environment: %s", bus_error_message(&error, r)); |
1822350d | 90 | |
8d451309 | 91 | return 0; |
1822350d KS |
92 | } |
93 | ||
8d451309 | 94 | static int vconsole_reload(sd_bus *bus) { |
4afd3348 | 95 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
1822350d | 96 | int r; |
1822350d KS |
97 | |
98 | assert(bus); | |
99 | ||
8d451309 | 100 | r = sd_bus_call_method(bus, |
1822350d KS |
101 | "org.freedesktop.systemd1", |
102 | "/org/freedesktop/systemd1", | |
103 | "org.freedesktop.systemd1.Manager", | |
8d451309 KS |
104 | "RestartUnit", |
105 | &error, | |
106 | NULL, | |
107 | "ss", "systemd-vconsole-setup.service", "replace"); | |
1822350d | 108 | |
8d451309 | 109 | if (r < 0) |
26a93376 | 110 | return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); |
4ae25393 YW |
111 | |
112 | return 0; | |
1822350d KS |
113 | } |
114 | ||
df4fd2c7 | 115 | static int vconsole_convert_to_x11_and_emit(Context *c, sd_bus_message *m) { |
78bd12a0 | 116 | int r; |
0732ef7a | 117 | |
df4fd2c7 YW |
118 | assert(m); |
119 | ||
120 | r = x11_read_data(c, m); | |
121 | if (r < 0) | |
122 | return r; | |
4e829d21 | 123 | |
4897d1dc ZJS |
124 | r = vconsole_convert_to_x11(c); |
125 | if (r <= 0) | |
126 | return r; | |
4e829d21 | 127 | |
4897d1dc ZJS |
128 | /* modified */ |
129 | r = x11_write_data(c); | |
130 | if (r < 0) | |
131 | return log_error_errno(r, "Failed to write X11 keyboard layout: %m"); | |
4e829d21 | 132 | |
df4fd2c7 | 133 | sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), |
4897d1dc ZJS |
134 | "/org/freedesktop/locale1", |
135 | "org.freedesktop.locale1", | |
136 | "X11Layout", "X11Model", "X11Variant", "X11Options", NULL); | |
4e829d21 | 137 | |
4897d1dc | 138 | return 1; |
4e829d21 ZJS |
139 | } |
140 | ||
df4fd2c7 | 141 | static int x11_convert_to_vconsole_and_emit(Context *c, sd_bus_message *m) { |
0732ef7a | 142 | int r; |
1822350d | 143 | |
df4fd2c7 YW |
144 | assert(m); |
145 | ||
146 | r = vconsole_read_data(c, m); | |
147 | if (r < 0) | |
148 | return r; | |
1822350d | 149 | |
4897d1dc ZJS |
150 | r = x11_convert_to_vconsole(c); |
151 | if (r <= 0) | |
152 | return r; | |
502f9614 | 153 | |
4897d1dc ZJS |
154 | /* modified */ |
155 | r = vconsole_write_data(c); | |
156 | if (r < 0) | |
157 | log_error_errno(r, "Failed to save virtual console keymap: %m"); | |
1822350d | 158 | |
df4fd2c7 | 159 | sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), |
4897d1dc ZJS |
160 | "/org/freedesktop/locale1", |
161 | "org.freedesktop.locale1", | |
162 | "VConsoleKeymap", "VConsoleKeymapToggle", NULL); | |
1822350d | 163 | |
df4fd2c7 | 164 | return vconsole_reload(sd_bus_message_get_bus(m)); |
1822350d KS |
165 | } |
166 | ||
ebcf1f97 LP |
167 | static int property_get_locale( |
168 | sd_bus *bus, | |
169 | const char *path, | |
170 | const char *interface, | |
171 | const char *property, | |
172 | sd_bus_message *reply, | |
173 | void *userdata, | |
174 | sd_bus_error *error) { | |
175 | ||
8d451309 | 176 | Context *c = userdata; |
b47d419c | 177 | _cleanup_strv_free_ char **l = NULL; |
df4fd2c7 YW |
178 | int p, q, r; |
179 | ||
180 | r = locale_read_data(c, reply); | |
181 | if (r < 0) | |
182 | return r; | |
1822350d | 183 | |
78c97cbe | 184 | l = new0(char*, _VARIABLE_LC_MAX+1); |
1822350d KS |
185 | if (!l) |
186 | return -ENOMEM; | |
187 | ||
78c97cbe | 188 | for (p = 0, q = 0; p < _VARIABLE_LC_MAX; p++) { |
1822350d | 189 | char *t; |
78c97cbe ZJS |
190 | const char *name; |
191 | ||
192 | name = locale_variable_to_string(p); | |
193 | assert(name); | |
1822350d | 194 | |
8d451309 | 195 | if (isempty(c->locale[p])) |
1822350d KS |
196 | continue; |
197 | ||
78c97cbe | 198 | if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0) |
1822350d | 199 | return -ENOMEM; |
1822350d | 200 | |
8d451309 | 201 | l[q++] = t; |
1822350d KS |
202 | } |
203 | ||
8d451309 | 204 | return sd_bus_message_append_strv(reply, l); |
1822350d KS |
205 | } |
206 | ||
df4fd2c7 YW |
207 | static int property_get_vconsole( |
208 | sd_bus *bus, | |
209 | const char *path, | |
210 | const char *interface, | |
211 | const char *property, | |
212 | sd_bus_message *reply, | |
213 | void *userdata, | |
214 | sd_bus_error *error) { | |
215 | ||
216 | Context *c = userdata; | |
217 | int r; | |
218 | ||
219 | r = vconsole_read_data(c, reply); | |
220 | if (r < 0) | |
221 | return r; | |
222 | ||
223 | if (streq(property, "VConsoleKeymap")) | |
224 | return sd_bus_message_append_basic(reply, 's', c->vc_keymap); | |
225 | else if (streq(property, "VConsoleKeymapToggle")) | |
226 | return sd_bus_message_append_basic(reply, 's', c->vc_keymap_toggle); | |
227 | ||
228 | return -EINVAL; | |
229 | } | |
230 | ||
231 | static int property_get_xkb( | |
232 | sd_bus *bus, | |
233 | const char *path, | |
234 | const char *interface, | |
235 | const char *property, | |
236 | sd_bus_message *reply, | |
237 | void *userdata, | |
238 | sd_bus_error *error) { | |
239 | ||
240 | Context *c = userdata; | |
241 | int r; | |
242 | ||
243 | r = x11_read_data(c, reply); | |
244 | if (r < 0) | |
245 | return r; | |
246 | ||
247 | if (streq(property, "X11Layout")) | |
248 | return sd_bus_message_append_basic(reply, 's', c->x11_layout); | |
249 | else if (streq(property, "X11Model")) | |
250 | return sd_bus_message_append_basic(reply, 's', c->x11_model); | |
251 | else if (streq(property, "X11Variant")) | |
252 | return sd_bus_message_append_basic(reply, 's', c->x11_variant); | |
253 | else if (streq(property, "X11Options")) | |
254 | return sd_bus_message_append_basic(reply, 's', c->x11_options); | |
255 | ||
256 | return -EINVAL; | |
257 | } | |
258 | ||
19070062 | 259 | static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
e6755a33 | 260 | _cleanup_(locale_variables_freep) char *new_locale[_VARIABLE_LC_MAX] = {}; |
df4fd2c7 | 261 | _cleanup_strv_free_ char **settings = NULL, **l = NULL; |
e6755a33 | 262 | Context *c = userdata; |
8d451309 | 263 | bool modified = false; |
df4fd2c7 | 264 | int interactive, p, r; |
e6755a33 | 265 | char **i; |
1822350d | 266 | |
19070062 LP |
267 | assert(m); |
268 | assert(c); | |
269 | ||
8d451309 KS |
270 | r = bus_message_read_strv_extend(m, &l); |
271 | if (r < 0) | |
ebcf1f97 | 272 | return r; |
1822350d | 273 | |
8d451309 KS |
274 | r = sd_bus_message_read_basic(m, 'b', &interactive); |
275 | if (r < 0) | |
ebcf1f97 | 276 | return r; |
1822350d | 277 | |
4156e767 YW |
278 | /* If single locale without variable name is provided, then we assume it is LANG=. */ |
279 | if (strv_length(l) == 1 && !strchr(*l, '=')) { | |
280 | if (!locale_is_valid(*l)) | |
281 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data."); | |
282 | ||
283 | new_locale[VARIABLE_LANG] = strdup(*l); | |
284 | if (!new_locale[VARIABLE_LANG]) | |
285 | return -ENOMEM; | |
286 | ||
287 | l = strv_free(l); | |
288 | } | |
289 | ||
df4fd2c7 | 290 | /* Check whether a variable is valid */ |
8d451309 KS |
291 | STRV_FOREACH(i, l) { |
292 | bool valid = false; | |
1822350d | 293 | |
78c97cbe | 294 | for (p = 0; p < _VARIABLE_LC_MAX; p++) { |
8d451309 | 295 | size_t k; |
78c97cbe | 296 | const char *name; |
1822350d | 297 | |
78c97cbe ZJS |
298 | name = locale_variable_to_string(p); |
299 | assert(name); | |
300 | ||
301 | k = strlen(name); | |
302 | if (startswith(*i, name) && | |
8d451309 | 303 | (*i)[k] == '=' && |
75683450 | 304 | locale_is_valid((*i) + k + 1)) { |
8d451309 | 305 | valid = true; |
1822350d | 306 | |
df4fd2c7 YW |
307 | new_locale[p] = strdup((*i) + k + 1); |
308 | if (!new_locale[p]) | |
309 | return -ENOMEM; | |
1822350d | 310 | |
8d451309 KS |
311 | break; |
312 | } | |
1822350d KS |
313 | } |
314 | ||
8d451309 | 315 | if (!valid) |
ebcf1f97 | 316 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data."); |
8d451309 | 317 | } |
1822350d | 318 | |
4e829d21 ZJS |
319 | /* If LANG was specified, but not LANGUAGE, check if we should |
320 | * set it based on the language fallback table. */ | |
df4fd2c7 YW |
321 | if (!isempty(new_locale[VARIABLE_LANG]) && |
322 | isempty(new_locale[VARIABLE_LANGUAGE])) { | |
4e829d21 ZJS |
323 | _cleanup_free_ char *language = NULL; |
324 | ||
df4fd2c7 | 325 | (void) find_language_fallback(new_locale[VARIABLE_LANG], &language); |
4e829d21 | 326 | if (language) { |
df4fd2c7 YW |
327 | log_debug("Converted LANG=%s to LANGUAGE=%s", new_locale[VARIABLE_LANG], language); |
328 | free_and_replace(new_locale[VARIABLE_LANGUAGE], language); | |
4e829d21 ZJS |
329 | } |
330 | } | |
331 | ||
df4fd2c7 YW |
332 | r = locale_read_data(c, m); |
333 | if (r < 0) { | |
334 | log_error_errno(r, "Failed to read locale data: %m"); | |
335 | return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Failed to read locale data"); | |
336 | } | |
8d451309 | 337 | |
df4fd2c7 YW |
338 | /* Merge with the current settings */ |
339 | for (p = 0; p < _VARIABLE_LC_MAX; p++) | |
340 | if (!isempty(c->locale[p]) && isempty(new_locale[p])) { | |
341 | new_locale[p] = strdup(c->locale[p]); | |
342 | if (!new_locale[p]) | |
343 | return -ENOMEM; | |
344 | } | |
1822350d | 345 | |
df4fd2c7 | 346 | locale_simplify(new_locale); |
1822350d | 347 | |
df4fd2c7 YW |
348 | for (p = 0; p < _VARIABLE_LC_MAX; p++) |
349 | if (!streq_ptr(c->locale[p], new_locale[p])) { | |
350 | modified = true; | |
351 | break; | |
8d451309 | 352 | } |
1822350d | 353 | |
df4fd2c7 YW |
354 | if (!modified) { |
355 | log_debug("Locale settings were not modified."); | |
356 | return sd_bus_reply_method_return(m, NULL); | |
357 | } | |
1822350d | 358 | |
df4fd2c7 YW |
359 | r = bus_verify_polkit_async( |
360 | m, | |
361 | CAP_SYS_ADMIN, | |
362 | "org.freedesktop.locale1.set-locale", | |
363 | NULL, | |
364 | interactive, | |
365 | UID_INVALID, | |
af7865c1 | 366 | &c->polkit_registry, |
df4fd2c7 YW |
367 | error); |
368 | if (r < 0) | |
369 | return r; | |
370 | if (r == 0) | |
371 | return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ | |
1822350d | 372 | |
df4fd2c7 YW |
373 | for (p = 0; p < _VARIABLE_LC_MAX; p++) |
374 | free_and_replace(c->locale[p], new_locale[p]); | |
1822350d | 375 | |
df4fd2c7 YW |
376 | r = locale_write_data(c, &settings); |
377 | if (r < 0) { | |
378 | log_error_errno(r, "Failed to set locale: %m"); | |
379 | return sd_bus_error_set_errnof(error, r, "Failed to set locale: %m"); | |
380 | } | |
502f9614 | 381 | |
df4fd2c7 | 382 | (void) locale_update_system_manager(c, sd_bus_message_get_bus(m)); |
1822350d | 383 | |
df4fd2c7 YW |
384 | if (settings) { |
385 | _cleanup_free_ char *line; | |
386 | ||
387 | line = strv_join(settings, ", "); | |
388 | log_info("Changed locale to %s.", strnull(line)); | |
502f9614 | 389 | } else |
df4fd2c7 YW |
390 | log_info("Changed locale to unset."); |
391 | ||
392 | (void) sd_bus_emit_properties_changed( | |
393 | sd_bus_message_get_bus(m), | |
394 | "/org/freedesktop/locale1", | |
395 | "org.freedesktop.locale1", | |
396 | "Locale", NULL); | |
502f9614 | 397 | |
df2d202e | 398 | return sd_bus_reply_method_return(m, NULL); |
8d451309 | 399 | } |
1822350d | 400 | |
19070062 | 401 | static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
8d451309 | 402 | Context *c = userdata; |
8d451309 | 403 | const char *keymap, *keymap_toggle; |
b47ff73b | 404 | int convert, interactive, r; |
f2cc3753 | 405 | |
19070062 LP |
406 | assert(m); |
407 | assert(c); | |
408 | ||
8d451309 KS |
409 | r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive); |
410 | if (r < 0) | |
ebcf1f97 | 411 | return r; |
1822350d | 412 | |
3c6f7c34 LP |
413 | keymap = empty_to_null(keymap); |
414 | keymap_toggle = empty_to_null(keymap_toggle); | |
1822350d | 415 | |
df4fd2c7 YW |
416 | r = vconsole_read_data(c, m); |
417 | if (r < 0) { | |
418 | log_error_errno(r, "Failed to read virtual console keymap data: %m"); | |
419 | return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Failed to read virtual console keymap data"); | |
420 | } | |
421 | ||
b47ff73b YW |
422 | if (streq_ptr(keymap, c->vc_keymap) && |
423 | streq_ptr(keymap_toggle, c->vc_keymap_toggle)) | |
424 | return sd_bus_reply_method_return(m, NULL); | |
1822350d | 425 | |
b47ff73b YW |
426 | if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) || |
427 | (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle)))) | |
428 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Received invalid keymap data"); | |
1822350d | 429 | |
b47ff73b YW |
430 | r = bus_verify_polkit_async( |
431 | m, | |
432 | CAP_SYS_ADMIN, | |
433 | "org.freedesktop.locale1.set-keyboard", | |
434 | NULL, | |
435 | interactive, | |
436 | UID_INVALID, | |
af7865c1 | 437 | &c->polkit_registry, |
b47ff73b YW |
438 | error); |
439 | if (r < 0) | |
440 | return r; | |
441 | if (r == 0) | |
442 | return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ | |
1822350d | 443 | |
b47ff73b YW |
444 | if (free_and_strdup(&c->vc_keymap, keymap) < 0 || |
445 | free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0) | |
446 | return -ENOMEM; | |
0b507b17 | 447 | |
b47ff73b YW |
448 | r = vconsole_write_data(c); |
449 | if (r < 0) { | |
450 | log_error_errno(r, "Failed to set virtual console keymap: %m"); | |
451 | return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %m"); | |
452 | } | |
1822350d | 453 | |
b47ff73b YW |
454 | log_info("Changed virtual console keymap to '%s' toggle '%s'", |
455 | strempty(c->vc_keymap), strempty(c->vc_keymap_toggle)); | |
1822350d | 456 | |
26a93376 | 457 | (void) vconsole_reload(sd_bus_message_get_bus(m)); |
1822350d | 458 | |
b47ff73b YW |
459 | (void) sd_bus_emit_properties_changed( |
460 | sd_bus_message_get_bus(m), | |
461 | "/org/freedesktop/locale1", | |
462 | "org.freedesktop.locale1", | |
463 | "VConsoleKeymap", "VConsoleKeymapToggle", NULL); | |
1822350d | 464 | |
b47ff73b | 465 | if (convert) { |
df4fd2c7 | 466 | r = vconsole_convert_to_x11_and_emit(c, m); |
b47ff73b YW |
467 | if (r < 0) |
468 | log_error_errno(r, "Failed to convert keymap data: %m"); | |
8d451309 | 469 | } |
1822350d | 470 | |
df2d202e | 471 | return sd_bus_reply_method_return(m, NULL); |
8d451309 | 472 | } |
1822350d | 473 | |
349cc4a5 | 474 | #if HAVE_XKBCOMMON |
5de34470 | 475 | |
3cb063fd | 476 | _printf_(3, 0) |
d4f5a1f4 | 477 | static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) { |
8433e339 JS |
478 | const char *fmt; |
479 | ||
63c372cb | 480 | fmt = strjoina("libxkbcommon: ", format); |
032b7541 ZJS |
481 | #pragma GCC diagnostic push |
482 | #pragma GCC diagnostic ignored "-Wformat-nonliteral" | |
8433e339 | 483 | log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args); |
032b7541 | 484 | #pragma GCC diagnostic pop |
d4f5a1f4 DH |
485 | } |
486 | ||
5de34470 LP |
487 | #define LOAD_SYMBOL(symbol, dl, name) \ |
488 | ({ \ | |
489 | (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \ | |
490 | (symbol) ? 0 : -EOPNOTSUPP; \ | |
491 | }) | |
492 | ||
d4f5a1f4 | 493 | static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { |
5de34470 LP |
494 | |
495 | /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge | |
496 | * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function | |
497 | * pointers to the shared library are below: */ | |
498 | ||
499 | struct xkb_context* (*symbol_xkb_context_new)(enum xkb_context_flags flags) = NULL; | |
500 | void (*symbol_xkb_context_unref)(struct xkb_context *context) = NULL; | |
501 | 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; | |
502 | 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; | |
503 | void (*symbol_xkb_keymap_unref)(struct xkb_keymap *keymap) = NULL; | |
504 | ||
d4f5a1f4 DH |
505 | const struct xkb_rule_names rmlvo = { |
506 | .model = model, | |
507 | .layout = layout, | |
508 | .variant = variant, | |
509 | .options = options, | |
510 | }; | |
511 | struct xkb_context *ctx = NULL; | |
512 | struct xkb_keymap *km = NULL; | |
5de34470 | 513 | void *dl; |
d4f5a1f4 DH |
514 | int r; |
515 | ||
5de34470 LP |
516 | /* Compile keymap from RMLVO information to check out its validity */ |
517 | ||
518 | dl = dlopen("libxkbcommon.so.0", RTLD_LAZY); | |
519 | if (!dl) | |
520 | return -EOPNOTSUPP; | |
521 | ||
522 | r = LOAD_SYMBOL(symbol_xkb_context_new, dl, "xkb_context_new"); | |
523 | if (r < 0) | |
524 | goto finish; | |
525 | ||
526 | r = LOAD_SYMBOL(symbol_xkb_context_unref, dl, "xkb_context_unref"); | |
527 | if (r < 0) | |
528 | goto finish; | |
529 | ||
530 | r = LOAD_SYMBOL(symbol_xkb_context_set_log_fn, dl, "xkb_context_set_log_fn"); | |
531 | if (r < 0) | |
532 | goto finish; | |
d4f5a1f4 | 533 | |
5de34470 LP |
534 | r = LOAD_SYMBOL(symbol_xkb_keymap_new_from_names, dl, "xkb_keymap_new_from_names"); |
535 | if (r < 0) | |
536 | goto finish; | |
537 | ||
538 | r = LOAD_SYMBOL(symbol_xkb_keymap_unref, dl, "xkb_keymap_unref"); | |
539 | if (r < 0) | |
540 | goto finish; | |
541 | ||
542 | ctx = symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES); | |
d4f5a1f4 DH |
543 | if (!ctx) { |
544 | r = -ENOMEM; | |
5de34470 | 545 | goto finish; |
d4f5a1f4 DH |
546 | } |
547 | ||
5de34470 | 548 | symbol_xkb_context_set_log_fn(ctx, log_xkb); |
d4f5a1f4 | 549 | |
5de34470 | 550 | km = symbol_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS); |
d4f5a1f4 DH |
551 | if (!km) { |
552 | r = -EINVAL; | |
5de34470 | 553 | goto finish; |
d4f5a1f4 DH |
554 | } |
555 | ||
556 | r = 0; | |
557 | ||
5de34470 LP |
558 | finish: |
559 | if (symbol_xkb_keymap_unref && km) | |
560 | symbol_xkb_keymap_unref(km); | |
561 | ||
562 | if (symbol_xkb_context_unref && ctx) | |
563 | symbol_xkb_context_unref(ctx); | |
564 | ||
565 | (void) dlclose(dl); | |
d4f5a1f4 DH |
566 | return r; |
567 | } | |
5de34470 | 568 | |
d4f5a1f4 | 569 | #else |
5de34470 | 570 | |
d4f5a1f4 DH |
571 | static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { |
572 | return 0; | |
573 | } | |
5de34470 | 574 | |
d4f5a1f4 DH |
575 | #endif |
576 | ||
19070062 | 577 | static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
8d451309 | 578 | Context *c = userdata; |
8d451309 | 579 | const char *layout, *model, *variant, *options; |
b47ff73b | 580 | int convert, interactive, r; |
1822350d | 581 | |
19070062 LP |
582 | assert(m); |
583 | assert(c); | |
584 | ||
8d451309 KS |
585 | r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive); |
586 | if (r < 0) | |
ebcf1f97 | 587 | return r; |
1822350d | 588 | |
3c6f7c34 LP |
589 | layout = empty_to_null(layout); |
590 | model = empty_to_null(model); | |
591 | variant = empty_to_null(variant); | |
592 | options = empty_to_null(options); | |
1822350d | 593 | |
df4fd2c7 YW |
594 | r = x11_read_data(c, m); |
595 | if (r < 0) { | |
596 | log_error_errno(r, "Failed to read x11 keyboard layout data: %m"); | |
597 | return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Failed to read x11 keyboard layout data"); | |
598 | } | |
599 | ||
b47ff73b YW |
600 | if (streq_ptr(layout, c->x11_layout) && |
601 | streq_ptr(model, c->x11_model) && | |
602 | streq_ptr(variant, c->x11_variant) && | |
603 | streq_ptr(options, c->x11_options)) | |
604 | return sd_bus_reply_method_return(m, NULL); | |
605 | ||
606 | if ((layout && !string_is_safe(layout)) || | |
607 | (model && !string_is_safe(model)) || | |
608 | (variant && !string_is_safe(variant)) || | |
609 | (options && !string_is_safe(options))) | |
610 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Received invalid keyboard data"); | |
611 | ||
fe28d887 YW |
612 | r = verify_xkb_rmlvo(model, layout, variant, options); |
613 | if (r < 0) { | |
614 | log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m", | |
615 | strempty(model), strempty(layout), strempty(variant), strempty(options)); | |
616 | ||
617 | if (r == -EOPNOTSUPP) | |
618 | return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Local keyboard configuration not supported on this system."); | |
619 | ||
620 | return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Specified keymap cannot be compiled, refusing as invalid."); | |
621 | } | |
622 | ||
b47ff73b YW |
623 | r = bus_verify_polkit_async( |
624 | m, | |
625 | CAP_SYS_ADMIN, | |
626 | "org.freedesktop.locale1.set-keyboard", | |
627 | NULL, | |
628 | interactive, | |
629 | UID_INVALID, | |
af7865c1 | 630 | &c->polkit_registry, |
b47ff73b YW |
631 | error); |
632 | if (r < 0) | |
633 | return r; | |
634 | if (r == 0) | |
635 | return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ | |
1822350d | 636 | |
b47ff73b YW |
637 | if (free_and_strdup(&c->x11_layout, layout) < 0 || |
638 | free_and_strdup(&c->x11_model, model) < 0 || | |
639 | free_and_strdup(&c->x11_variant, variant) < 0 || | |
640 | free_and_strdup(&c->x11_options, options) < 0) | |
641 | return -ENOMEM; | |
1822350d | 642 | |
b47ff73b YW |
643 | r = x11_write_data(c); |
644 | if (r < 0) { | |
645 | log_error_errno(r, "Failed to set X11 keyboard layout: %m"); | |
646 | return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %m"); | |
647 | } | |
1822350d | 648 | |
b47ff73b YW |
649 | log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'", |
650 | strempty(c->x11_layout), | |
651 | strempty(c->x11_model), | |
652 | strempty(c->x11_variant), | |
653 | strempty(c->x11_options)); | |
1822350d | 654 | |
b47ff73b YW |
655 | (void) sd_bus_emit_properties_changed( |
656 | sd_bus_message_get_bus(m), | |
657 | "/org/freedesktop/locale1", | |
658 | "org.freedesktop.locale1", | |
659 | "X11Layout", "X11Model", "X11Variant", "X11Options", NULL); | |
1822350d | 660 | |
b47ff73b | 661 | if (convert) { |
df4fd2c7 | 662 | r = x11_convert_to_vconsole_and_emit(c, m); |
b47ff73b YW |
663 | if (r < 0) |
664 | log_error_errno(r, "Failed to convert keymap data: %m"); | |
1822350d KS |
665 | } |
666 | ||
df2d202e | 667 | return sd_bus_reply_method_return(m, NULL); |
1822350d KS |
668 | } |
669 | ||
8d451309 KS |
670 | static const sd_bus_vtable locale_vtable[] = { |
671 | SD_BUS_VTABLE_START(0), | |
6d1bd3b2 | 672 | SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), |
df4fd2c7 YW |
673 | SD_BUS_PROPERTY("X11Layout", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), |
674 | SD_BUS_PROPERTY("X11Model", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
675 | SD_BUS_PROPERTY("X11Variant", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
676 | SD_BUS_PROPERTY("X11Options", "s", property_get_xkb, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
677 | SD_BUS_PROPERTY("VConsoleKeymap", "s", property_get_vconsole, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
678 | SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", property_get_vconsole, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
adacb957 LP |
679 | SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED), |
680 | SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED), | |
681 | SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED), | |
8d451309 KS |
682 | SD_BUS_VTABLE_END |
683 | }; | |
684 | ||
685 | static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { | |
4afd3348 | 686 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; |
1822350d KS |
687 | int r; |
688 | ||
8d451309 KS |
689 | assert(c); |
690 | assert(event); | |
1822350d KS |
691 | assert(_bus); |
692 | ||
76b54375 | 693 | r = sd_bus_default_system(&bus); |
f647962d MS |
694 | if (r < 0) |
695 | return log_error_errno(r, "Failed to get system bus connection: %m"); | |
1822350d | 696 | |
19befb2d | 697 | r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c); |
f647962d MS |
698 | if (r < 0) |
699 | return log_error_errno(r, "Failed to register object: %m"); | |
1822350d | 700 | |
0c0b9306 | 701 | r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.locale1", 0, NULL, NULL); |
f647962d | 702 | if (r < 0) |
0c0b9306 | 703 | return log_error_errno(r, "Failed to request name: %m"); |
1822350d | 704 | |
8d451309 | 705 | r = sd_bus_attach_event(bus, event, 0); |
f647962d MS |
706 | if (r < 0) |
707 | return log_error_errno(r, "Failed to attach bus to event loop: %m"); | |
1822350d | 708 | |
1cc6c93a | 709 | *_bus = TAKE_PTR(bus); |
1822350d | 710 | |
8d451309 | 711 | return 0; |
1822350d KS |
712 | } |
713 | ||
c6f09e6a | 714 | static int run(int argc, char *argv[]) { |
6804d7a8 | 715 | _cleanup_(context_clear) Context context = { |
df4fd2c7 YW |
716 | .locale_mtime = USEC_INFINITY, |
717 | .vc_mtime = USEC_INFINITY, | |
718 | .x11_mtime = USEC_INFINITY, | |
719 | }; | |
4afd3348 LP |
720 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; |
721 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
1822350d | 722 | int r; |
1822350d | 723 | |
6bf3c61c | 724 | log_setup_service(); |
8d451309 | 725 | |
1822350d | 726 | umask(0022); |
c3dacc8b | 727 | mac_selinux_init(); |
1822350d | 728 | |
c6f09e6a YW |
729 | if (argc != 1) |
730 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); | |
1822350d | 731 | |
50008ae4 YW |
732 | assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); |
733 | ||
afc6adb5 | 734 | r = sd_event_default(&event); |
c6f09e6a YW |
735 | if (r < 0) |
736 | return log_error_errno(r, "Failed to allocate event loop: %m"); | |
1822350d | 737 | |
50008ae4 YW |
738 | (void) sd_event_set_watchdog(event, true); |
739 | ||
740 | r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); | |
c6f09e6a YW |
741 | if (r < 0) |
742 | return log_error_errno(r, "Failed to install SIGINT handler: %m"); | |
50008ae4 YW |
743 | |
744 | r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); | |
c6f09e6a YW |
745 | if (r < 0) |
746 | return log_error_errno(r, "Failed to install SIGTERM handler: %m"); | |
cde93897 | 747 | |
8d451309 | 748 | r = connect_bus(&context, event, &bus); |
1822350d | 749 | if (r < 0) |
c6f09e6a | 750 | return r; |
1822350d | 751 | |
37224a5f | 752 | r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL); |
4897d1dc | 753 | if (r < 0) |
c6f09e6a | 754 | return log_error_errno(r, "Failed to run event loop: %m"); |
1822350d | 755 | |
c6f09e6a | 756 | return 0; |
1822350d | 757 | } |
c6f09e6a YW |
758 | |
759 | DEFINE_MAIN_FUNCTION(run); |