/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. Copyright (C) 2014 David Herrmann systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #include #include #include #include #include #include #include #include #include #include "bus-util.h" #include "hashmap.h" #include "idev.h" #include "idev-internal.h" #include "macro.h" #include "udev-util.h" #include "util.h" typedef struct idev_evdev idev_evdev; typedef struct unmanaged_evdev unmanaged_evdev; typedef struct managed_evdev managed_evdev; struct idev_evdev { idev_element element; struct libevdev *evdev; int fd; sd_event_source *fd_src; sd_event_source *idle_src; bool unsync : 1; /* not in-sync with kernel */ bool resync : 1; /* re-syncing with kernel */ }; struct unmanaged_evdev { idev_evdev evdev; char *devnode; }; struct managed_evdev { idev_evdev evdev; dev_t devnum; sd_bus_slot *slot_pause_device; sd_bus_slot *slot_resume_device; sd_bus_slot *slot_take_device; bool requested : 1; /* TakeDevice() was sent */ bool acquired : 1; /* TakeDevice() was successful */ }; #define idev_evdev_from_element(_e) container_of((_e), idev_evdev, element) #define unmanaged_evdev_from_element(_e) \ container_of(idev_evdev_from_element(_e), unmanaged_evdev, evdev) #define managed_evdev_from_element(_e) \ container_of(idev_evdev_from_element(_e), managed_evdev, evdev) #define IDEV_EVDEV_INIT(_vtable, _session) ((idev_evdev){ \ .element = IDEV_ELEMENT_INIT((_vtable), (_session)), \ .fd = -1, \ }) #define IDEV_EVDEV_NAME_MAX (8 + DECIMAL_STR_MAX(unsigned) * 2) static const idev_element_vtable unmanaged_evdev_vtable; static const idev_element_vtable managed_evdev_vtable; static int idev_evdev_resume(idev_evdev *evdev, int dev_fd); static void idev_evdev_pause(idev_evdev *evdev, bool release); /* * Virtual Evdev Element * The virtual evdev element is the base class of all other evdev elements. It * uses libevdev to access the kernel evdev API. It supports asynchronous * access revocation, re-syncing if events got dropped and more. * This element cannot be used by itself. There must be a wrapper around it * which opens a file-descriptor and passes it to the virtual evdev element. */ static void idev_evdev_name(char *out, dev_t devnum) { /* @out must be at least of size IDEV_EVDEV_NAME_MAX */ sprintf(out, "evdev/%u:%u", major(devnum), minor(devnum)); } static int idev_evdev_raise(idev_evdev *evdev, struct input_event *event) { idev_data data = { .type = IDEV_DATA_EVDEV, .resync = evdev->resync, .evdev = { .event = *event, }, }; return idev_element_feed(&evdev->element, &data); } static void idev_evdev_hup(idev_evdev *evdev) { /* * On HUP, we close the current fd via idev_evdev_pause(). This drops * the event-sources from the main-loop and effectively puts the * element asleep. If the HUP is part of a hotplug-event, a following * udev-notification will destroy the element. Otherwise, the HUP is * either result of access-revokation or a serious error. * For unmanaged devices, we should never receive HUP (except for * unplug-events). But if we do, something went seriously wrong and we * shouldn't try to be clever. * Instead, we simply stay asleep and wait for the device to be * disabled and then re-enabled (or closed and re-opened). This will * re-open the device node and restart the device. * For managed devices, a HUP usually means our device-access was * revoked. In that case, we simply put the device asleep and wait for * logind to notify us once the device is alive again. logind also * passes us a new fd. Hence, we don't have to re-enable the device. * * Long story short: The only thing we have to do here, is close() the * file-descriptor and remove it from the main-loop. Everything else is * handled via additional events we receive. */ idev_evdev_pause(evdev, true); } static int idev_evdev_io(idev_evdev *evdev) { idev_element *e = &evdev->element; struct input_event ev; unsigned int flags; int r, error = 0; /* * Read input-events via libevdev until the input-queue is drained. In * case we're disabled, don't do anything. The input-queue might * overflow, but we don't care as we have to resync after wake-up, * anyway. * TODO: libevdev should give us a hint how many events to read. We * really want to avoid starvation, so we shouldn't read forever in * case we cannot keep up with the kernel. * TODO: Make sure libevdev always reports SYN_DROPPED to us, regardless * whether any event was synced afterwards. * TODO: Forward SYN_DROPPED to attached devices. */ flags = LIBEVDEV_READ_FLAG_NORMAL; while (e->enabled) { if (evdev->unsync) { /* immediately resync, even if in sync right now */ evdev->unsync = false; evdev->resync = false; flags = LIBEVDEV_READ_FLAG_NORMAL; r = libevdev_next_event(evdev->evdev, flags | LIBEVDEV_READ_FLAG_FORCE_SYNC, &ev); if (r < 0 && r != -EAGAIN) { r = 0; goto error; } else if (r != LIBEVDEV_READ_STATUS_SYNC) { log_debug("idev-evdev: %s/%s: cannot force resync: %d", e->session->name, e->name, r); } } else { r = libevdev_next_event(evdev->evdev, flags, &ev); } if (evdev->resync && r == -EAGAIN) { /* end of re-sync */ evdev->resync = false; flags = LIBEVDEV_READ_FLAG_NORMAL; } else if (r == -EAGAIN) { /* no data available */ break; } else if (r < 0) { /* read error */ goto error; } else if (r == LIBEVDEV_READ_STATUS_SYNC) { if (evdev->resync) { /* sync-event */ r = idev_evdev_raise(evdev, &ev); if (r != 0) { error = r; break; } } else { /* start of sync */ evdev->resync = true; flags = LIBEVDEV_READ_FLAG_SYNC; } } else { /* normal event */ r = idev_evdev_raise(evdev, &ev); if (r != 0) { error = r; break; } } } if (error < 0) log_debug("idev-evdev: %s/%s: error on data event: %s", e->session->name, e->name, strerror(-error)); return error; error: idev_evdev_hup(evdev); return 0; /* idev_evdev_hup() handles the error so discard it */ } static int idev_evdev_event_fn(sd_event_source *s, int fd, uint32_t revents, void *userdata) { idev_evdev *evdev = userdata; /* fetch data as long as EPOLLIN is signalled */ if (revents & EPOLLIN) return idev_evdev_io(evdev); if (revents & (EPOLLHUP | EPOLLERR)) idev_evdev_hup(evdev); return 0; } static int idev_evdev_idle_fn(sd_event_source *s, void *userdata) { idev_evdev *evdev = userdata; /* * The idle-event is raised whenever we have to re-sync the libevdev * state from the kernel. We simply call into idev_evdev_io() which * flushes the state and re-syncs it if @unsync is set. * State has to be synced whenever our view of the kernel device is * out of date. This is the case when we open the device, if the * kernel's receive buffer overflows, or on other exceptional * situations. Events during re-syncs must be forwarded to the upper * layers so they can update their view of the device. However, such * events must only be handled passively, as they might be out-of-order * and/or re-ordered. Therefore, we mark them as 'sync' events. */ if (!evdev->unsync) return 0; return idev_evdev_io(evdev); } static void idev_evdev_destroy(idev_evdev *evdev) { assert(evdev); assert(evdev->fd < 0); libevdev_free(evdev->evdev); evdev->evdev = NULL; } static void idev_evdev_enable(idev_evdev *evdev) { assert(evdev); assert(evdev->fd_src); assert(evdev->idle_src); sd_event_source_set_enabled(evdev->fd_src, SD_EVENT_ON); sd_event_source_set_enabled(evdev->idle_src, SD_EVENT_ONESHOT); } static void idev_evdev_disable(idev_evdev *evdev) { assert(evdev); assert(evdev->fd_src); assert(evdev->idle_src); sd_event_source_set_enabled(evdev->fd_src, SD_EVENT_OFF); sd_event_source_set_enabled(evdev->idle_src, SD_EVENT_OFF); } static int idev_evdev_resume(idev_evdev *evdev, int dev_fd) { idev_element *e = &evdev->element; _cleanup_close_ int fd = dev_fd; int r, flags; if (fd < 0 || evdev->fd == fd) { fd = -1; if (evdev->fd >= 0 && e->n_open > 0 && e->enabled) idev_evdev_enable(evdev); return 0; } idev_evdev_pause(evdev, true); log_debug("idev-evdev: %s/%s: resume", e->session->name, e->name); r = fd_nonblock(fd, true); if (r < 0) return r; r = fd_cloexec(fd, true); if (r < 0) return r; flags = fcntl(fd, F_GETFL, 0); if (flags < 0) return -errno; flags &= O_ACCMODE; if (flags == O_WRONLY) return -EACCES; evdev->element.readable = true; evdev->element.writable = true; if (flags == O_RDONLY) evdev->element.writable = false; else if (flags == O_WRONLY) evdev->element.readable = false; /* * TODO: We *MUST* re-sync the device so we get a delta of the changed * state while we didn't read events from the device. This works just * fine with libevdev_change_fd(), however, libevdev_new_from_fd() (or * libevdev_set_fd()) don't pass us events for the initial device * state. So even if we force a re-sync, we will not get the delta for * the initial device state. * We really need to fix libevdev to support that! */ if (evdev->evdev) r = libevdev_change_fd(evdev->evdev, fd); else r = libevdev_new_from_fd(fd, &evdev->evdev); if (r < 0) return r; r = sd_event_add_io(e->session->context->event, &evdev->fd_src, fd, EPOLLHUP | EPOLLERR | EPOLLIN, idev_evdev_event_fn, evdev); if (r < 0) return r; r = sd_event_add_defer(e->session->context->event, &evdev->idle_src, idev_evdev_idle_fn, evdev); if (r < 0) { evdev->fd_src = sd_event_source_unref(evdev->fd_src); return r; } if (e->n_open < 1 || !e->enabled) { sd_event_source_set_enabled(evdev->fd_src, SD_EVENT_OFF); sd_event_source_set_enabled(evdev->idle_src, SD_EVENT_OFF); } evdev->unsync = true; evdev->fd = fd; fd = -1; return 0; } static void idev_evdev_pause(idev_evdev *evdev, bool release) { idev_element *e = &evdev->element; if (evdev->fd < 0) return; log_debug("idev-evdev: %s/%s: pause", e->session->name, e->name); if (release) { evdev->idle_src = sd_event_source_unref(evdev->idle_src); evdev->fd_src = sd_event_source_unref(evdev->fd_src); evdev->fd = safe_close(evdev->fd); } else { idev_evdev_disable(evdev); } } /* * Unmanaged Evdev Element * The unmanaged evdev element opens the evdev node for a given input device * directly (/dev/input/eventX) and thus needs sufficient privileges. It opens * the device only if we really require it and releases it as soon as we're * disabled or closed. * The unmanaged element can be used in all situations where you have direct * access to input device nodes. Unlike managed evdev elements, it can be used * outside of user sessions and in emergency situations where logind is not * available. */ static void unmanaged_evdev_resume(idev_element *e) { unmanaged_evdev *eu = unmanaged_evdev_from_element(e); int r, fd; /* * Unmanaged devices can be acquired on-demand. Therefore, don't * acquire it unless someone opened the device *and* we're enabled. */ if (e->n_open < 1 || !e->enabled) return; fd = eu->evdev.fd; if (fd < 0) { fd = open(eu->devnode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); if (fd < 0) { if (errno != EACCES && errno != EPERM) { log_debug("idev-evdev: %s/%s: cannot open node %s: %m", e->session->name, e->name, eu->devnode); return; } fd = open(eu->devnode, O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); if (fd < 0) { log_debug("idev-evdev: %s/%s: cannot open node %s: %m", e->session->name, e->name, eu->devnode); return; } e->readable = true; e->writable = false; } else { e->readable = true; e->writable = true; } } r = idev_evdev_resume(&eu->evdev, fd); if (r < 0) log_debug("idev-evdev: %s/%s: cannot resume: %s", e->session->name, e->name, strerror(-r)); } static void unmanaged_evdev_pause(idev_element *e) { unmanaged_evdev *eu = unmanaged_evdev_from_element(e); /* * Release the device if the device is disabled or there is no-one who * opened it. This guarantees we stay only available if we're opened * *and* enabled. */ idev_evdev_pause(&eu->evdev, true); } static int unmanaged_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud) { _cleanup_(idev_element_freep) idev_element *e = NULL; char name[IDEV_EVDEV_NAME_MAX]; unmanaged_evdev *eu; const char *devnode; dev_t devnum; int r; assert_return(s, -EINVAL); assert_return(ud, -EINVAL); devnode = udev_device_get_devnode(ud); devnum = udev_device_get_devnum(ud); if (!devnode || devnum == 0) return -ENODEV; idev_evdev_name(name, devnum); eu = new0(unmanaged_evdev, 1); if (!eu) return -ENOMEM; e = &eu->evdev.element; eu->evdev = IDEV_EVDEV_INIT(&unmanaged_evdev_vtable, s); eu->devnode = strdup(devnode); if (!eu->devnode) return -ENOMEM; r = idev_element_add(e, name); if (r < 0) return r; if (out) *out = e; e = NULL; return 0; } static void unmanaged_evdev_free(idev_element *e) { unmanaged_evdev *eu = unmanaged_evdev_from_element(e); idev_evdev_destroy(&eu->evdev); free(eu->devnode); free(eu); } static const idev_element_vtable unmanaged_evdev_vtable = { .free = unmanaged_evdev_free, .enable = unmanaged_evdev_resume, .disable = unmanaged_evdev_pause, .open = unmanaged_evdev_resume, .close = unmanaged_evdev_pause, }; /* * Managed Evdev Element * The managed evdev element uses systemd-logind to acquire evdev devices. This * means, we do not open the device node /dev/input/eventX directly. Instead, * logind passes us a file-descriptor whenever our session is activated. Thus, * we don't need access to the device node directly. * Furthermore, whenever the session is put asleep, logind revokes the * file-descriptor so we loose access to the device. * Managed evdev elements should be preferred over unmanaged elements whenever * you run inside a user session with exclusive device access. */ static int managed_evdev_take_device_fn(sd_bus *bus, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { managed_evdev *em = userdata; idev_element *e = &em->evdev.element; idev_session *s = e->session; int r, paused, fd; em->slot_take_device = sd_bus_slot_unref(em->slot_take_device); if (sd_bus_message_is_method_error(reply, NULL)) { const sd_bus_error *error = sd_bus_message_get_error(reply); log_debug("idev-evdev: %s/%s: TakeDevice failed: %s: %s", s->name, e->name, error->name, error->message); return 0; } em->acquired = true; r = sd_bus_message_read(reply, "hb", &fd, &paused); if (r < 0) { log_debug("idev-evdev: %s/%s: erroneous TakeDevice reply", s->name, e->name); return 0; } /* If the device is paused, ignore it; we will get the next fd via * ResumeDevice signals. */ if (paused) return 0; fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); if (fd < 0) { log_debug("idev-evdev: %s/%s: cannot duplicate evdev fd: %m", s->name, e->name); return 0; } r = idev_evdev_resume(&em->evdev, fd); if (r < 0) log_debug("idev-evdev: %s/%s: cannot resume: %s", s->name, e->name, strerror(-r)); return 0; } static void managed_evdev_resume(idev_element *e) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; managed_evdev *em = managed_evdev_from_element(e); idev_session *s = e->session; idev_context *c = s->context; int r; /* * Acquiring managed devices is heavy, so do it only once we're * enabled *and* opened by someone. */ if (e->n_open < 1 || !e->enabled) return; /* bail out if already pending */ if (em->requested) return; r = sd_bus_message_new_method_call(c->sysbus, &m, "org.freedesktop.login1", s->path, "org.freedesktop.login1.Session", "TakeDevice"); if (r < 0) goto error; r = sd_bus_message_append(m, "uu", major(em->devnum), minor(em->devnum)); if (r < 0) goto error; r = sd_bus_call_async(c->sysbus, &em->slot_take_device, m, managed_evdev_take_device_fn, em, 0); if (r < 0) goto error; em->requested = true; return; error: log_debug("idev-evdev: %s/%s: cannot send TakeDevice request: %s", s->name, e->name, strerror(-r)); } static void managed_evdev_pause(idev_element *e) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; managed_evdev *em = managed_evdev_from_element(e); idev_session *s = e->session; idev_context *c = s->context; int r; /* * Releasing managed devices is heavy. Once acquired, we get * notifications for sleep/wake-up events, so there's no reason to * release it if disabled but opened. However, if a device is closed, * we release it immediately as we don't care for sleep/wake-up events * then (even if we're actually enabled). */ idev_evdev_pause(&em->evdev, false); if (e->n_open > 0 || !em->requested) return; /* * If TakeDevice() is pending or was successful, make sure to * release the device again. We don't care for return-values, * so send it without waiting or callbacks. * If a failed TakeDevice() is pending, but someone else took * the device on the same bus-connection, we might incorrectly * release their device. This is an unlikely race, though. * Furthermore, you really shouldn't have two users of the * controller-API on the same session, on the same devices, *AND* on * the same bus-connection. So we don't care for that race.. */ idev_evdev_pause(&em->evdev, true); em->requested = false; if (!em->acquired && !em->slot_take_device) return; em->slot_take_device = sd_bus_slot_unref(em->slot_take_device); em->acquired = false; r = sd_bus_message_new_method_call(c->sysbus, &m, "org.freedesktop.login1", s->path, "org.freedesktop.login1.Session", "ReleaseDevice"); if (r >= 0) { r = sd_bus_message_append(m, "uu", major(em->devnum), minor(em->devnum)); if (r >= 0) r = sd_bus_send(c->sysbus, m, NULL); } if (r < 0 && r != -ENOTCONN) log_debug("idev-evdev: %s/%s: cannot send ReleaseDevice: %s", s->name, e->name, strerror(-r)); } static int managed_evdev_pause_device_fn(sd_bus *bus, sd_bus_message *signal, void *userdata, sd_bus_error *ret_error) { managed_evdev *em = userdata; idev_element *e = &em->evdev.element; idev_session *s = e->session; idev_context *c = s->context; uint32_t major, minor; const char *mode; int r; /* * We get PauseDevice() signals from logind whenever a device we * requested was, or is about to be, paused. Arguments are major/minor * number of the device and the mode of the operation. * In case the event is not about our device, we ignore it. Otherwise, * we treat it as asynchronous access-revocation (as if we got HUP on * the device fd). Note that we might have already treated the HUP * event via EPOLLHUP, whichever comes first. * * @mode can be one of the following: * "pause": The device is about to be paused. We must react * immediately and respond with PauseDeviceComplete(). Once * we replied, logind will pause the device. Note that * logind might apply any kind of timeout and force pause * the device if we don't respond in a timely manner. In * this case, we will receive a second PauseDevice event * with @mode set to "force" (or similar). * "force": The device was disabled forecfully by logind. Access is * already revoked. This is just an asynchronous * notification so we can put the device asleep (in case * we didn't already notice the access revocation). * "gone": This is like "force" but is sent if the device was * paused due to a device-removal event. * * We always handle PauseDevice signals as "force" as we properly * support asynchronous access revocation, anyway. But in case logind * sent mode "pause", we also call PauseDeviceComplete() to immediately * acknowledge the request. */ r = sd_bus_message_read(signal, "uus", &major, &minor, &mode); if (r < 0) { log_debug("idev-evdev: %s/%s: erroneous PauseDevice signal", s->name, e->name); return 0; } /* not our device? */ if (makedev(major, minor) != em->devnum) return 0; idev_evdev_pause(&em->evdev, true); if (streq(mode, "pause")) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; /* * Sending PauseDeviceComplete() is racy if logind triggers the * timeout. That is, if we take too long and logind pauses the * device by sending a forced PauseDevice, our * PauseDeviceComplete call will be stray. That's fine, though. * logind ignores such stray calls. Only if logind also sent a * further PauseDevice() signal, it might match our call * incorrectly to the newer PauseDevice(). That's fine, too, as * we handle that event asynchronously, anyway. Therefore, * whatever happens, we're fine. Yay! */ r = sd_bus_message_new_method_call(c->sysbus, &m, "org.freedesktop.login1", s->path, "org.freedesktop.login1.Session", "PauseDeviceComplete"); if (r >= 0) { r = sd_bus_message_append(m, "uu", major, minor); if (r >= 0) r = sd_bus_send(c->sysbus, m, NULL); } if (r < 0) log_debug("idev-evdev: %s/%s: cannot send PauseDeviceComplete: %s", s->name, e->name, strerror(-r)); } return 0; } static int managed_evdev_resume_device_fn(sd_bus *bus, sd_bus_message *signal, void *userdata, sd_bus_error *ret_error) { managed_evdev *em = userdata; idev_element *e = &em->evdev.element; idev_session *s = e->session; uint32_t major, minor; int r, fd; /* * We get ResumeDevice signals whenever logind resumed a previously * paused device. The arguments contain the major/minor number of the * related device and a new file-descriptor for the freshly opened * device-node. * If the signal is not about our device, we simply ignore it. * Otherwise, we take the file-descriptor and immediately resume the * device. */ r = sd_bus_message_read(signal, "uuh", &major, &minor, &fd); if (r < 0) { log_debug("idev-evdev: %s/%s: erroneous ResumeDevice signal", s->name, e->name); return 0; } /* not our device? */ if (makedev(major, minor) != em->devnum) return 0; fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); if (fd < 0) { log_debug("idev-evdev: %s/%s: cannot duplicate evdev fd: %m", s->name, e->name); return 0; } r = idev_evdev_resume(&em->evdev, fd); if (r < 0) log_debug("idev-evdev: %s/%s: cannot resume: %s", s->name, e->name, strerror(-r)); return 0; } static int managed_evdev_setup_bus(managed_evdev *em) { idev_element *e = &em->evdev.element; idev_session *s = e->session; idev_context *c = s->context; _cleanup_free_ char *match = NULL; int r; match = strjoin("type='signal'," "sender='org.freedesktop.login1'," "interface='org.freedesktop.login1.Session'," "member='PauseDevice'," "path='", s->path, "'", NULL); if (!match) return -ENOMEM; r = sd_bus_add_match(c->sysbus, &em->slot_pause_device, match, managed_evdev_pause_device_fn, em); if (r < 0) return r; free(match); match = strjoin("type='signal'," "sender='org.freedesktop.login1'," "interface='org.freedesktop.login1.Session'," "member='ResumeDevice'," "path='", s->path, "'", NULL); if (!match) return -ENOMEM; r = sd_bus_add_match(c->sysbus, &em->slot_resume_device, match, managed_evdev_resume_device_fn, em); if (r < 0) return r; return 0; } static int managed_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud) { _cleanup_(idev_element_freep) idev_element *e = NULL; char name[IDEV_EVDEV_NAME_MAX]; managed_evdev *em; dev_t devnum; int r; assert_return(s, -EINVAL); assert_return(s->context->sysbus, -EINVAL); assert_return(s->managed, -EINVAL); assert_return(s->context->sysbus, -EINVAL); assert_return(ud, -EINVAL); devnum = udev_device_get_devnum(ud); if (devnum == 0) return -ENODEV; idev_evdev_name(name, devnum); em = new0(managed_evdev, 1); if (!em) return -ENOMEM; e = &em->evdev.element; em->evdev = IDEV_EVDEV_INIT(&managed_evdev_vtable, s); em->devnum = devnum; r = managed_evdev_setup_bus(em); if (r < 0) return r; r = idev_element_add(e, name); if (r < 0) return r; if (out) *out = e; e = NULL; return 0; } static void managed_evdev_free(idev_element *e) { managed_evdev *em = managed_evdev_from_element(e); em->slot_resume_device = sd_bus_slot_unref(em->slot_resume_device); em->slot_pause_device = sd_bus_slot_unref(em->slot_pause_device); idev_evdev_destroy(&em->evdev); free(em); } static const idev_element_vtable managed_evdev_vtable = { .free = managed_evdev_free, .enable = managed_evdev_resume, .disable = managed_evdev_pause, .open = managed_evdev_resume, .close = managed_evdev_pause, }; /* * Generic Constructor * Instead of relying on the caller to choose between managed and unmanaged * evdev devices, the idev_evdev_new() constructor does that for you (by * looking at s->managed). */ bool idev_is_evdev(idev_element *e) { return e && (e->vtable == &unmanaged_evdev_vtable || e->vtable == &managed_evdev_vtable); } idev_element *idev_find_evdev(idev_session *s, dev_t devnum) { char name[IDEV_EVDEV_NAME_MAX]; assert_return(s, NULL); assert_return(devnum != 0, NULL); idev_evdev_name(name, devnum); return idev_find_element(s, name); } int idev_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud) { assert_return(s, -EINVAL); assert_return(ud, -EINVAL); return s->managed ? managed_evdev_new(out, s, ud) : unmanaged_evdev_new(out, s, ud); }