xref: /OK3568_Linux_fs/external/xserver/hw/xfree86/os-support/linux/systemd-logind.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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