1 /*
2 * Copyright © 2013 Red Hat Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 * Author: Hans de Goede <hdegoede@redhat.com>
24 */
25
26 #ifdef HAVE_XORG_CONFIG_H
27 #include <xorg-config.h>
28 #endif
29
30 #include <dbus/dbus.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34
35 #include "os.h"
36 #include "dbus-core.h"
37 #include "linux.h"
38 #include "xf86.h"
39 #include "xf86platformBus.h"
40 #include "xf86Xinput.h"
41 #include "globals.h"
42
43 #include "systemd-logind.h"
44
45 struct systemd_logind_info {
46 DBusConnection *conn;
47 char *session;
48 Bool active;
49 Bool vt_active;
50 };
51
52 static struct systemd_logind_info logind_info;
53
54 static InputInfoPtr
systemd_logind_find_info_ptr_by_devnum(InputInfoPtr start,int major,int minor)55 systemd_logind_find_info_ptr_by_devnum(InputInfoPtr start,
56 int major, int minor)
57 {
58 InputInfoPtr pInfo;
59
60 for (pInfo = start; pInfo; pInfo = pInfo->next)
61 if (pInfo->major == major && pInfo->minor == minor &&
62 (pInfo->flags & XI86_SERVER_FD))
63 return pInfo;
64
65 return NULL;
66 }
67
68 static void
systemd_logind_set_input_fd_for_all_devs(int major,int minor,int fd,Bool enable)69 systemd_logind_set_input_fd_for_all_devs(int major, int minor, int fd,
70 Bool enable)
71 {
72 InputInfoPtr pInfo;
73
74 pInfo = systemd_logind_find_info_ptr_by_devnum(xf86InputDevs, major, minor);
75 while (pInfo) {
76 pInfo->fd = fd;
77 pInfo->options = xf86ReplaceIntOption(pInfo->options, "fd", fd);
78 if (enable)
79 xf86EnableInputDeviceForVTSwitch(pInfo);
80
81 pInfo = systemd_logind_find_info_ptr_by_devnum(pInfo->next, major, minor);
82 }
83 }
84
85 int
systemd_logind_take_fd(int _major,int _minor,const char * path,Bool * paused_ret)86 systemd_logind_take_fd(int _major, int _minor, const char *path,
87 Bool *paused_ret)
88 {
89 struct systemd_logind_info *info = &logind_info;
90 InputInfoPtr pInfo;
91 DBusError error;
92 DBusMessage *msg = NULL;
93 DBusMessage *reply = NULL;
94 dbus_int32_t major = _major;
95 dbus_int32_t minor = _minor;
96 dbus_bool_t paused;
97 int fd = -1;
98
99 if (!info->session || major == 0)
100 return -1;
101
102 /* logind does not support mouse devs (with evdev we don't need them) */
103 if (strstr(path, "mouse"))
104 return -1;
105
106 /* Check if we already have an InputInfo entry with this major, minor
107 * (shared device-nodes happen ie with Wacom tablets). */
108 pInfo = systemd_logind_find_info_ptr_by_devnum(xf86InputDevs, major, minor);
109 if (pInfo) {
110 LogMessage(X_INFO, "systemd-logind: returning pre-existing fd for %s %u:%u\n",
111 path, major, minor);
112 *paused_ret = FALSE;
113 return pInfo->fd;
114 }
115
116 dbus_error_init(&error);
117
118 msg = dbus_message_new_method_call("org.freedesktop.login1", info->session,
119 "org.freedesktop.login1.Session", "TakeDevice");
120 if (!msg) {
121 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
122 goto cleanup;
123 }
124
125 if (!dbus_message_append_args(msg, DBUS_TYPE_UINT32, &major,
126 DBUS_TYPE_UINT32, &minor,
127 DBUS_TYPE_INVALID)) {
128 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
129 goto cleanup;
130 }
131
132 reply = dbus_connection_send_with_reply_and_block(info->conn, msg,
133 DBUS_TIMEOUT_USE_DEFAULT, &error);
134 if (!reply) {
135 LogMessage(X_ERROR, "systemd-logind: failed to take device %s: %s\n",
136 path, error.message);
137 goto cleanup;
138 }
139
140 if (!dbus_message_get_args(reply, &error,
141 DBUS_TYPE_UNIX_FD, &fd,
142 DBUS_TYPE_BOOLEAN, &paused,
143 DBUS_TYPE_INVALID)) {
144 LogMessage(X_ERROR, "systemd-logind: TakeDevice %s: %s\n",
145 path, error.message);
146 goto cleanup;
147 }
148
149 *paused_ret = paused;
150
151 LogMessage(X_INFO, "systemd-logind: got fd for %s %u:%u fd %d paused %d\n",
152 path, major, minor, fd, paused);
153
154 cleanup:
155 if (msg)
156 dbus_message_unref(msg);
157 if (reply)
158 dbus_message_unref(reply);
159 dbus_error_free(&error);
160
161 return fd;
162 }
163
164 void
systemd_logind_release_fd(int _major,int _minor,int fd)165 systemd_logind_release_fd(int _major, int _minor, int fd)
166 {
167 struct systemd_logind_info *info = &logind_info;
168 InputInfoPtr pInfo;
169 DBusError error;
170 DBusMessage *msg = NULL;
171 DBusMessage *reply = NULL;
172 dbus_int32_t major = _major;
173 dbus_int32_t minor = _minor;
174 int matches = 0;
175
176 if (!info->session || major == 0)
177 goto close;
178
179 /* Only release the fd if there is only 1 InputInfo left for this major
180 * and minor, otherwise other InputInfo's are still referencing the fd. */
181 pInfo = systemd_logind_find_info_ptr_by_devnum(xf86InputDevs, major, minor);
182 while (pInfo) {
183 matches++;
184 pInfo = systemd_logind_find_info_ptr_by_devnum(pInfo->next, major, minor);
185 }
186 if (matches > 1) {
187 LogMessage(X_INFO, "systemd-logind: not releasing fd for %u:%u, still in use\n", major, minor);
188 return;
189 }
190
191 LogMessage(X_INFO, "systemd-logind: releasing fd for %u:%u\n", major, minor);
192
193 dbus_error_init(&error);
194
195 msg = dbus_message_new_method_call("org.freedesktop.login1", info->session,
196 "org.freedesktop.login1.Session", "ReleaseDevice");
197 if (!msg) {
198 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
199 goto cleanup;
200 }
201
202 if (!dbus_message_append_args(msg, DBUS_TYPE_UINT32, &major,
203 DBUS_TYPE_UINT32, &minor,
204 DBUS_TYPE_INVALID)) {
205 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
206 goto cleanup;
207 }
208
209 reply = dbus_connection_send_with_reply_and_block(info->conn, msg,
210 DBUS_TIMEOUT_USE_DEFAULT, &error);
211 if (!reply)
212 LogMessage(X_ERROR, "systemd-logind: failed to release device: %s\n",
213 error.message);
214
215 cleanup:
216 if (msg)
217 dbus_message_unref(msg);
218 if (reply)
219 dbus_message_unref(reply);
220 dbus_error_free(&error);
221 close:
222 if (fd != -1)
223 close(fd);
224 }
225
226 int
systemd_logind_controls_session(void)227 systemd_logind_controls_session(void)
228 {
229 return logind_info.session ? 1 : 0;
230 }
231
232 void
systemd_logind_vtenter(void)233 systemd_logind_vtenter(void)
234 {
235 struct systemd_logind_info *info = &logind_info;
236 InputInfoPtr pInfo;
237 int i;
238
239 if (!info->session)
240 return; /* Not using systemd-logind */
241
242 if (!info->active)
243 return; /* Session not active */
244
245 if (info->vt_active)
246 return; /* Already did vtenter */
247
248 for (i = 0; i < xf86_num_platform_devices; i++) {
249 if (xf86_platform_devices[i].flags & XF86_PDEV_PAUSED)
250 break;
251 }
252 if (i != xf86_num_platform_devices)
253 return; /* Some drm nodes are still paused wait for resume */
254
255 xf86VTEnter();
256 info->vt_active = TRUE;
257
258 /* Activate any input devices which were resumed before the drm nodes */
259 for (pInfo = xf86InputDevs; pInfo; pInfo = pInfo->next)
260 if ((pInfo->flags & XI86_SERVER_FD) && pInfo->fd != -1)
261 xf86EnableInputDeviceForVTSwitch(pInfo);
262
263 /* Do delayed input probing, this must be done after the above enabling */
264 xf86InputEnableVTProbe();
265 }
266
267 static void
systemd_logind_ack_pause(struct systemd_logind_info * info,dbus_int32_t minor,dbus_int32_t major)268 systemd_logind_ack_pause(struct systemd_logind_info *info,
269 dbus_int32_t minor, dbus_int32_t major)
270 {
271 DBusError error;
272 DBusMessage *msg = NULL;
273 DBusMessage *reply = NULL;
274
275 dbus_error_init(&error);
276
277 msg = dbus_message_new_method_call("org.freedesktop.login1", info->session,
278 "org.freedesktop.login1.Session", "PauseDeviceComplete");
279 if (!msg) {
280 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
281 goto cleanup;
282 }
283
284 if (!dbus_message_append_args(msg, DBUS_TYPE_UINT32, &major,
285 DBUS_TYPE_UINT32, &minor,
286 DBUS_TYPE_INVALID)) {
287 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
288 goto cleanup;
289 }
290
291 reply = dbus_connection_send_with_reply_and_block(info->conn, msg,
292 DBUS_TIMEOUT_USE_DEFAULT, &error);
293 if (!reply)
294 LogMessage(X_ERROR, "systemd-logind: failed to ack pause: %s\n",
295 error.message);
296
297 cleanup:
298 if (msg)
299 dbus_message_unref(msg);
300 if (reply)
301 dbus_message_unref(reply);
302 dbus_error_free(&error);
303 }
304
305 static DBusHandlerResult
message_filter(DBusConnection * connection,DBusMessage * message,void * data)306 message_filter(DBusConnection * connection, DBusMessage * message, void *data)
307 {
308 struct systemd_logind_info *info = data;
309 struct xf86_platform_device *pdev = NULL;
310 InputInfoPtr pInfo = NULL;
311 int ack = 0, pause = 0, fd = -1;
312 DBusError error;
313 dbus_int32_t major, minor;
314 char *pause_str;
315
316 if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_SIGNAL)
317 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
318
319 dbus_error_init(&error);
320
321 if (dbus_message_is_signal(message,
322 "org.freedesktop.DBus", "NameOwnerChanged")) {
323 char *name, *old_owner, *new_owner;
324
325 dbus_message_get_args(message, &error,
326 DBUS_TYPE_STRING, &name,
327 DBUS_TYPE_STRING, &old_owner,
328 DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID);
329 if (dbus_error_is_set(&error)) {
330 LogMessage(X_ERROR, "systemd-logind: NameOwnerChanged: %s\n",
331 error.message);
332 dbus_error_free(&error);
333 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
334 }
335
336 if (name && strcmp(name, "org.freedesktop.login1") == 0)
337 FatalError("systemd-logind disappeared (stopped/restarted?)\n");
338
339 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
340 }
341
342 if (strcmp(dbus_message_get_path(message), info->session) != 0)
343 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
344
345 if (dbus_message_is_signal(message, "org.freedesktop.login1.Session",
346 "PauseDevice")) {
347 if (!dbus_message_get_args(message, &error,
348 DBUS_TYPE_UINT32, &major,
349 DBUS_TYPE_UINT32, &minor,
350 DBUS_TYPE_STRING, &pause_str,
351 DBUS_TYPE_INVALID)) {
352 LogMessage(X_ERROR, "systemd-logind: PauseDevice: %s\n",
353 error.message);
354 dbus_error_free(&error);
355 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
356 }
357
358 if (strcmp(pause_str, "pause") == 0) {
359 pause = 1;
360 ack = 1;
361 }
362 else if (strcmp(pause_str, "force") == 0) {
363 pause = 1;
364 }
365 else if (strcmp(pause_str, "gone") == 0) {
366 /* Device removal is handled through udev */
367 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
368 }
369 else {
370 LogMessage(X_WARNING, "systemd-logind: unknown pause type: %s\n",
371 pause_str);
372 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
373 }
374 }
375 else if (dbus_message_is_signal(message, "org.freedesktop.login1.Session",
376 "ResumeDevice")) {
377 if (!dbus_message_get_args(message, &error,
378 DBUS_TYPE_UINT32, &major,
379 DBUS_TYPE_UINT32, &minor,
380 DBUS_TYPE_UNIX_FD, &fd,
381 DBUS_TYPE_INVALID)) {
382 LogMessage(X_ERROR, "systemd-logind: ResumeDevice: %s\n",
383 error.message);
384 dbus_error_free(&error);
385 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
386 }
387 } else
388 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
389
390 LogMessage(X_INFO, "systemd-logind: got %s for %u:%u\n",
391 pause ? "pause" : "resume", major, minor);
392
393 pdev = xf86_find_platform_device_by_devnum(major, minor);
394 if (!pdev)
395 pInfo = systemd_logind_find_info_ptr_by_devnum(xf86InputDevs,
396 major, minor);
397 if (!pdev && !pInfo) {
398 LogMessage(X_WARNING, "systemd-logind: could not find dev %u:%u\n",
399 major, minor);
400 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
401 }
402
403 if (pause) {
404 /* Our VT_PROCESS usage guarantees we've already given up the vt */
405 info->active = info->vt_active = FALSE;
406 /* Note the actual vtleave has already been handled by xf86Events.c */
407 if (pdev)
408 pdev->flags |= XF86_PDEV_PAUSED;
409 else {
410 close(pInfo->fd);
411 systemd_logind_set_input_fd_for_all_devs(major, minor, -1, FALSE);
412 }
413 if (ack)
414 systemd_logind_ack_pause(info, major, minor);
415 }
416 else {
417 /* info->vt_active gets set by systemd_logind_vtenter() */
418 info->active = TRUE;
419
420 if (pdev)
421 pdev->flags &= ~XF86_PDEV_PAUSED;
422 else
423 systemd_logind_set_input_fd_for_all_devs(major, minor, fd,
424 info->vt_active);
425
426 /* Always call vtenter(), in case there are only legacy video devs */
427 systemd_logind_vtenter();
428 }
429 return DBUS_HANDLER_RESULT_HANDLED;
430 }
431
432 static void
connect_hook(DBusConnection * connection,void * data)433 connect_hook(DBusConnection *connection, void *data)
434 {
435 struct systemd_logind_info *info = data;
436 DBusError error;
437 DBusMessage *msg = NULL;
438 DBusMessage *reply = NULL;
439 dbus_int32_t arg;
440 char *session = NULL;
441
442 dbus_error_init(&error);
443
444 msg = dbus_message_new_method_call("org.freedesktop.login1",
445 "/org/freedesktop/login1", "org.freedesktop.login1.Manager",
446 "GetSessionByPID");
447 if (!msg) {
448 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
449 goto cleanup;
450 }
451
452 arg = getpid();
453 if (!dbus_message_append_args(msg, DBUS_TYPE_UINT32, &arg,
454 DBUS_TYPE_INVALID)) {
455 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
456 goto cleanup;
457 }
458
459 reply = dbus_connection_send_with_reply_and_block(connection, msg,
460 DBUS_TIMEOUT_USE_DEFAULT, &error);
461 if (!reply) {
462 LogMessage(X_ERROR, "systemd-logind: failed to get session: %s\n",
463 error.message);
464 goto cleanup;
465 }
466 dbus_message_unref(msg);
467
468 if (!dbus_message_get_args(reply, &error, DBUS_TYPE_OBJECT_PATH, &session,
469 DBUS_TYPE_INVALID)) {
470 LogMessage(X_ERROR, "systemd-logind: GetSessionByPID: %s\n",
471 error.message);
472 goto cleanup;
473 }
474 session = XNFstrdup(session);
475
476 dbus_message_unref(reply);
477 reply = NULL;
478
479
480 msg = dbus_message_new_method_call("org.freedesktop.login1",
481 session, "org.freedesktop.login1.Session", "TakeControl");
482 if (!msg) {
483 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
484 goto cleanup;
485 }
486
487 arg = FALSE; /* Don't forcibly take over over the session */
488 if (!dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &arg,
489 DBUS_TYPE_INVALID)) {
490 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
491 goto cleanup;
492 }
493
494 reply = dbus_connection_send_with_reply_and_block(connection, msg,
495 DBUS_TIMEOUT_USE_DEFAULT, &error);
496 if (!reply) {
497 LogMessage(X_ERROR, "systemd-logind: TakeControl failed: %s\n",
498 error.message);
499 goto cleanup;
500 }
501
502 dbus_bus_add_match(connection,
503 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus'",
504 &error);
505 if (dbus_error_is_set(&error)) {
506 LogMessage(X_ERROR, "systemd-logind: could not add match: %s\n",
507 error.message);
508 goto cleanup;
509 }
510
511 dbus_bus_add_match(connection,
512 "type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Session',member='PauseDevice'",
513 &error);
514 if (dbus_error_is_set(&error)) {
515 LogMessage(X_ERROR, "systemd-logind: could not add match: %s\n",
516 error.message);
517 goto cleanup;
518 }
519
520 dbus_bus_add_match(connection,
521 "type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Session',member='ResumeDevice'",
522 &error);
523 if (dbus_error_is_set(&error)) {
524 LogMessage(X_ERROR, "systemd-logind: could not add match: %s\n",
525 error.message);
526 goto cleanup;
527 }
528
529 /*
530 * HdG: This is not useful with systemd <= 208 since the signal only
531 * contains invalidated property names there, rather than property, val
532 * pairs as it should. Instead we just use the first resume / pause now.
533 */
534 #if 0
535 snprintf(match, sizeof(match),
536 "type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path='%s'",
537 session);
538 dbus_bus_add_match(connection, match, &error);
539 if (dbus_error_is_set(&error)) {
540 LogMessage(X_ERROR, "systemd-logind: could not add match: %s\n",
541 error.message);
542 goto cleanup;
543 }
544 #endif
545
546 if (!dbus_connection_add_filter(connection, message_filter, info, NULL)) {
547 LogMessage(X_ERROR, "systemd-logind: could not add filter: %s\n",
548 error.message);
549 goto cleanup;
550 }
551
552 LogMessage(X_INFO, "systemd-logind: took control of session %s\n",
553 session);
554 info->conn = connection;
555 info->session = session;
556 info->vt_active = info->active = TRUE; /* The server owns the vt during init */
557 session = NULL;
558
559 cleanup:
560 free(session);
561 if (msg)
562 dbus_message_unref(msg);
563 if (reply)
564 dbus_message_unref(reply);
565 dbus_error_free(&error);
566 }
567
568 static void
systemd_logind_release_control(struct systemd_logind_info * info)569 systemd_logind_release_control(struct systemd_logind_info *info)
570 {
571 DBusError error;
572 DBusMessage *msg = NULL;
573 DBusMessage *reply = NULL;
574
575 dbus_error_init(&error);
576
577 msg = dbus_message_new_method_call("org.freedesktop.login1",
578 info->session, "org.freedesktop.login1.Session", "ReleaseControl");
579 if (!msg) {
580 LogMessage(X_ERROR, "systemd-logind: out of memory\n");
581 goto cleanup;
582 }
583
584 reply = dbus_connection_send_with_reply_and_block(info->conn, msg,
585 DBUS_TIMEOUT_USE_DEFAULT, &error);
586 if (!reply) {
587 LogMessage(X_ERROR, "systemd-logind: ReleaseControl failed: %s\n",
588 error.message);
589 goto cleanup;
590 }
591
592 cleanup:
593 if (msg)
594 dbus_message_unref(msg);
595 if (reply)
596 dbus_message_unref(reply);
597 dbus_error_free(&error);
598 }
599
600 static void
disconnect_hook(void * data)601 disconnect_hook(void *data)
602 {
603 struct systemd_logind_info *info = data;
604
605 free(info->session);
606 info->session = NULL;
607 info->conn = NULL;
608 }
609
610 static struct dbus_core_hook core_hook = {
611 .connect = connect_hook,
612 .disconnect = disconnect_hook,
613 .data = &logind_info,
614 };
615
616 int
systemd_logind_init(void)617 systemd_logind_init(void)
618 {
619 if (!ServerIsNotSeat0() && linux_parse_vt_settings(TRUE) && !linux_get_keeptty()) {
620 LogMessage(X_INFO,
621 "systemd-logind: logind integration requires -keeptty and "
622 "-keeptty was not provided, disabling logind integration\n");
623 return 1;
624 }
625
626 return dbus_core_add_hook(&core_hook);
627 }
628
629 void
systemd_logind_fini(void)630 systemd_logind_fini(void)
631 {
632 if (logind_info.session)
633 systemd_logind_release_control(&logind_info);
634
635 dbus_core_remove_hook(&core_hook);
636 }
637