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