/*-*- 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 "bus-util.h" #include "event-util.h" #include "macro.h" #include "set.h" #include "sysview.h" #include "sysview-internal.h" #include "udev-util.h" #include "util.h" static int context_raise_session_control(sysview_context *c, sysview_session *session, int error); /* * Devices */ sysview_device *sysview_find_device(sysview_context *c, const char *name) { assert_return(c, NULL); assert_return(name, NULL); return hashmap_get(c->device_map, name); } int sysview_device_new(sysview_device **out, sysview_seat *seat, const char *name) { _cleanup_(sysview_device_freep) sysview_device *device = NULL; int r; assert_return(seat, -EINVAL); assert_return(name, -EINVAL); device = new0(sysview_device, 1); if (!device) return -ENOMEM; device->seat = seat; device->type = (unsigned)-1; device->name = strdup(name); if (!device->name) return -ENOMEM; r = hashmap_put(seat->context->device_map, device->name, device); if (r < 0) return r; r = hashmap_put(seat->device_map, device->name, device); if (r < 0) return r; if (out) *out = device; device = NULL; return 0; } sysview_device *sysview_device_free(sysview_device *device) { if (!device) return NULL; if (device->name) { hashmap_remove_value(device->seat->device_map, device->name, device); hashmap_remove_value(device->seat->context->device_map, device->name, device); } switch (device->type) { case SYSVIEW_DEVICE_EVDEV: device->evdev.ud = udev_device_unref(device->evdev.ud); break; case SYSVIEW_DEVICE_DRM: device->drm.ud = udev_device_unref(device->drm.ud); break; } free(device); return NULL; } unsigned int sysview_device_get_type(sysview_device *device) { assert_return(device, (unsigned)-1); return device->type; } struct udev_device *sysview_device_get_ud(sysview_device *device) { assert_return(device, NULL); switch (device->type) { case SYSVIEW_DEVICE_EVDEV: return device->evdev.ud; case SYSVIEW_DEVICE_DRM: return device->drm.ud; default: assert_return(0, NULL); } } static int device_new_ud(sysview_device **out, sysview_seat *seat, unsigned int type, struct udev_device *ud) { _cleanup_(sysview_device_freep) sysview_device *device = NULL; int r; assert_return(seat, -EINVAL); assert_return(ud, -EINVAL); r = sysview_device_new(&device, seat, udev_device_get_syspath(ud)); if (r < 0) return r; device->type = type; switch (type) { case SYSVIEW_DEVICE_EVDEV: device->evdev.ud = udev_device_ref(ud); break; case SYSVIEW_DEVICE_DRM: device->drm.ud = udev_device_ref(ud); break; default: assert_not_reached("sysview: invalid udev-device type"); } if (out) *out = device; device = NULL; return 0; } /* * Sessions */ sysview_session *sysview_find_session(sysview_context *c, const char *name) { assert_return(c, NULL); assert_return(name, NULL); return hashmap_get(c->session_map, name); } int sysview_session_new(sysview_session **out, sysview_seat *seat, const char *name) { _cleanup_(sysview_session_freep) sysview_session *session = NULL; int r; assert_return(seat, -EINVAL); session = new0(sysview_session, 1); if (!session) return -ENOMEM; session->seat = seat; if (name) { /* * If a name is given, we require it to be a logind session * name. The session will be put in managed mode and we use * logind to request controller access. */ session->name = strdup(name); if (!session->name) return -ENOMEM; r = sd_bus_path_encode("/org/freedesktop/login1/session", session->name, &session->path); if (r < 0) return r; session->custom = false;; } else { /* * No session name was given. We assume this is an unmanaged * session controlled by the application. We don't use logind * at all and leave session management to the application. The * name of the session-object is set to a unique random string * that does not clash with the logind namespace. */ r = asprintf(&session->name, "@custom%" PRIu64, ++seat->context->custom_sid); if (r < 0) return -ENOMEM; session->custom = true; } r = hashmap_put(seat->context->session_map, session->name, session); if (r < 0) return r; r = hashmap_put(seat->session_map, session->name, session); if (r < 0) return r; if (out) *out = session; session = NULL; return 0; } sysview_session *sysview_session_free(sysview_session *session) { if (!session) return NULL; assert(!session->public); assert(!session->wants_control); if (session->name) { hashmap_remove_value(session->seat->session_map, session->name, session); hashmap_remove_value(session->seat->context->session_map, session->name, session); } free(session->path); free(session->name); free(session); return NULL; } const char *sysview_session_get_name(sysview_session *session) { assert_return(session, NULL); return session->name; } static int session_take_control_fn(sd_bus *bus, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { sysview_session *session = userdata; int error; session->slot_take_control = sd_bus_slot_unref(session->slot_take_control); if (sd_bus_message_is_method_error(reply, NULL)) { const sd_bus_error *e = sd_bus_message_get_error(reply); log_debug("sysview: %s: TakeControl failed: %s: %s", session->name, e->name, e->message); error = sd_bus_error_get_errno(e); } else { session->has_control = true; error = 0; } return context_raise_session_control(session->seat->context, session, error); } int sysview_session_take_control(sysview_session *session) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; int r; assert_return(session, -EINVAL); assert_return(!session->custom, -EINVAL); if (session->wants_control) return 0; r = sd_bus_message_new_method_call(session->seat->context->sysbus, &m, "org.freedesktop.login1", session->path, "org.freedesktop.login1.Session", "TakeControl"); if (r < 0) return r; r = sd_bus_message_append(m, "b", 0); if (r < 0) return r; r = sd_bus_call_async(session->seat->context->sysbus, &session->slot_take_control, m, session_take_control_fn, session, 0); if (r < 0) return r; session->wants_control = true; return 0; } void sysview_session_release_control(sysview_session *session) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; int r; assert(session); assert(!session->custom); if (!session->wants_control) return; session->wants_control = false; if (!session->has_control && !session->slot_take_control) return; session->has_control = false; session->slot_take_control = sd_bus_slot_unref(session->slot_take_control); r = sd_bus_message_new_method_call(session->seat->context->sysbus, &m, "org.freedesktop.login1", session->path, "org.freedesktop.login1.Session", "ReleaseControl"); if (r >= 0) r = sd_bus_send(session->seat->context->sysbus, m, NULL); if (r < 0 && r != -ENOTCONN) log_debug("sysview: %s: cannot send ReleaseControl: %s", session->name, strerror(-r)); } /* * Seats */ sysview_seat *sysview_find_seat(sysview_context *c, const char *name) { assert_return(c, NULL); assert_return(name, NULL); return hashmap_get(c->seat_map, name); } int sysview_seat_new(sysview_seat **out, sysview_context *c, const char *name) { _cleanup_(sysview_seat_freep) sysview_seat *seat = NULL; int r; assert_return(c, -EINVAL); assert_return(name, -EINVAL); seat = new0(sysview_seat, 1); if (!seat) return -ENOMEM; seat->context = c; seat->name = strdup(name); if (!seat->name) return -ENOMEM; seat->session_map = hashmap_new(string_hash_func, string_compare_func); if (!seat->session_map) return -ENOMEM; seat->device_map = hashmap_new(string_hash_func, string_compare_func); if (!seat->device_map) return -ENOMEM; r = hashmap_put(c->seat_map, seat->name, seat); if (r < 0) return r; if (out) *out = seat; seat = NULL; return 0; } sysview_seat *sysview_seat_free(sysview_seat *seat) { if (!seat) return NULL; assert(!seat->public); assert(hashmap_size(seat->device_map) == 0); assert(hashmap_size(seat->session_map) == 0); if (seat->name) hashmap_remove_value(seat->context->seat_map, seat->name, seat); hashmap_free(seat->device_map); hashmap_free(seat->session_map); free(seat->name); free(seat); return NULL; } const char *sysview_seat_get_name(sysview_seat *seat) { assert_return(seat, NULL); return seat->name; } /* * Contexts */ static int context_raise(sysview_context *c, sysview_event *event, int def) { return c->running ? c->event_fn(c, c->userdata, event) : def; } static int context_raise_seat_add(sysview_context *c, sysview_seat *seat) { sysview_event event = { .type = SYSVIEW_EVENT_SEAT_ADD, .seat_add = { .seat = seat, } }; return context_raise(c, &event, 0); } static int context_raise_seat_remove(sysview_context *c, sysview_seat *seat) { sysview_event event = { .type = SYSVIEW_EVENT_SEAT_REMOVE, .seat_remove = { .seat = seat, } }; return context_raise(c, &event, 0); } static int context_raise_session_filter(sysview_context *c, const char *id, const char *seatid, const char *username, unsigned int uid) { sysview_event event = { .type = SYSVIEW_EVENT_SESSION_FILTER, .session_filter = { .id = id, .seatid = seatid, .username = username, .uid = uid, } }; return context_raise(c, &event, 1); } static int context_raise_session_add(sysview_context *c, sysview_session *session) { sysview_event event = { .type = SYSVIEW_EVENT_SESSION_ADD, .session_add = { .session = session, } }; return context_raise(c, &event, 0); } static int context_raise_session_remove(sysview_context *c, sysview_session *session) { sysview_event event = { .type = SYSVIEW_EVENT_SESSION_REMOVE, .session_remove = { .session = session, } }; return context_raise(c, &event, 0); } static int context_raise_session_control(sysview_context *c, sysview_session *session, int error) { sysview_event event = { .type = SYSVIEW_EVENT_SESSION_CONTROL, .session_control = { .session = session, .error = error, } }; return context_raise(c, &event, 0); } static int context_raise_session_attach(sysview_context *c, sysview_session *session, sysview_device *device) { sysview_event event = { .type = SYSVIEW_EVENT_SESSION_ATTACH, .session_attach = { .session = session, .device = device, } }; return context_raise(c, &event, 0); } static int context_raise_session_detach(sysview_context *c, sysview_session *session, sysview_device *device) { sysview_event event = { .type = SYSVIEW_EVENT_SESSION_DETACH, .session_detach = { .session = session, .device = device, } }; return context_raise(c, &event, 0); } static int context_add_device(sysview_context *c, sysview_device *device) { sysview_session *session; int r, error = 0; Iterator i; assert(c); assert(device); log_debug("sysview: add device '%s' on seat '%s'", device->name, device->seat->name); HASHMAP_FOREACH(session, device->seat->session_map, i) { if (!session->public) continue; r = context_raise_session_attach(c, session, device); if (r != 0) error = r; } if (error < 0) log_debug("sysview: error while adding device '%s': %s", device->name, strerror(-r)); return error; } static int context_remove_device(sysview_context *c, sysview_device *device) { sysview_session *session; int r, error = 0; Iterator i; assert(c); assert(device); log_debug("sysview: remove device '%s'", device->name); HASHMAP_FOREACH(session, device->seat->session_map, i) { if (!session->public) continue; r = context_raise_session_detach(c, session, device); if (r != 0) error = r; } if (error < 0) log_debug("sysview: error while removing device '%s': %s", device->name, strerror(-r)); sysview_device_free(device); return error; } static int context_add_session(sysview_context *c, sysview_seat *seat, const char *id) { sysview_session *session; sysview_device *device; int r, error = 0; Iterator i; assert(c); assert(seat); assert(id); session = sysview_find_session(c, id); if (session) return 0; log_debug("sysview: add session '%s' on seat '%s'", id, seat->name); r = sysview_session_new(&session, seat, id); if (r < 0) goto error; if (!seat->scanned) { r = sysview_context_rescan(c); if (r < 0) goto error; } if (seat->public) { session->public = true; r = context_raise_session_add(c, session); if (r != 0) { session->public = false; goto error; } HASHMAP_FOREACH(device, seat->device_map, i) { r = context_raise_session_attach(c, session, device); if (r != 0) error = r; } r = error; if (r != 0) goto error; } return 0; error: if (r < 0) log_debug("sysview: error while adding session '%s': %s", id, strerror(-r)); return r; } static int context_remove_session(sysview_context *c, sysview_session *session) { sysview_device *device; int r, error = 0; Iterator i; assert(c); assert(session); log_debug("sysview: remove session '%s'", session->name); if (session->public) { HASHMAP_FOREACH(device, session->seat->device_map, i) { r = context_raise_session_detach(c, session, device); if (r != 0) error = r; } session->public = false; r = context_raise_session_remove(c, session); if (r != 0) error = r; } if (!session->custom) sysview_session_release_control(session); if (error < 0) log_debug("sysview: error while removing session '%s': %s", session->name, strerror(-error)); sysview_session_free(session); return error; } static int context_add_seat(sysview_context *c, const char *id) { sysview_seat *seat; int r; assert(c); assert(id); seat = sysview_find_seat(c, id); if (seat) return 0; log_debug("sysview: add seat '%s'", id); r = sysview_seat_new(&seat, c, id); if (r < 0) goto error; seat->public = true; r = context_raise_seat_add(c, seat); if (r != 0) { seat->public = false; goto error; } return 0; error: if (r < 0) log_debug("sysview: error while adding seat '%s': %s", id, strerror(-r)); return r; } static int context_remove_seat(sysview_context *c, sysview_seat *seat) { sysview_session *session; sysview_device *device; int r, error = 0; assert(c); assert(seat); log_debug("sysview: remove seat '%s'", seat->name); while ((device = hashmap_first(seat->device_map))) { r = context_remove_device(c, device); if (r != 0) error = r; } while ((session = hashmap_first(seat->session_map))) { r = context_remove_session(c, session); if (r != 0) error = r; } if (seat->public) { seat->public = false; r = context_raise_seat_remove(c, seat); if (r != 0) error = r; } if (error < 0) log_debug("sysview: error while removing seat '%s': %s", seat->name, strerror(-error)); sysview_seat_free(seat); return error; } int sysview_context_new(sysview_context **out, unsigned int flags, sd_event *event, sd_bus *sysbus, struct udev *ud) { _cleanup_(sysview_context_freep) sysview_context *c = NULL; int r; assert_return(out, -EINVAL); assert_return(event, -EINVAL); log_debug("sysview: new"); c = new0(sysview_context, 1); if (!c) return -ENOMEM; c->event = sd_event_ref(event); if (flags & SYSVIEW_CONTEXT_SCAN_LOGIND) c->scan_logind = true; if (flags & SYSVIEW_CONTEXT_SCAN_EVDEV) c->scan_evdev = true; if (flags & SYSVIEW_CONTEXT_SCAN_DRM) c->scan_drm = true; if (sysbus) { c->sysbus = sd_bus_ref(sysbus); } else if (c->scan_logind) { r = sd_bus_open_system(&c->sysbus); if (r < 0) return r; } if (ud) { c->ud = udev_ref(ud); } else if (c->scan_evdev || c->scan_drm) { errno = 0; c->ud = udev_new(); if (!c->ud) return errno > 0 ? -errno : -EFAULT; } c->seat_map = hashmap_new(string_hash_func, string_compare_func); if (!c->seat_map) return -ENOMEM; c->session_map = hashmap_new(string_hash_func, string_compare_func); if (!c->session_map) return -ENOMEM; c->device_map = hashmap_new(string_hash_func, string_compare_func); if (!c->device_map) return -ENOMEM; *out = c; c = NULL; return 0; } sysview_context *sysview_context_free(sysview_context *c) { if (!c) return NULL; log_debug("sysview: free"); sysview_context_stop(c); assert(hashmap_size(c->device_map) == 0); assert(hashmap_size(c->session_map) == 0); assert(hashmap_size(c->seat_map) == 0); hashmap_free(c->device_map); hashmap_free(c->session_map); hashmap_free(c->seat_map); c->ud = udev_unref(c->ud); c->sysbus = sd_bus_unref(c->sysbus); c->event = sd_event_unref(c->event); free(c); return NULL; } static int context_ud_prepare_monitor(sysview_context *c, struct udev_monitor *m) { int r; if (c->scan_evdev) { r = udev_monitor_filter_add_match_subsystem_devtype(m, "input", NULL); if (r < 0) return r; } if (c->scan_drm) { r = udev_monitor_filter_add_match_subsystem_devtype(m, "drm", NULL); if (r < 0) return r; } return r; } static int context_ud_prepare_scan(sysview_context *c, struct udev_enumerate *e) { int r; if (c->scan_evdev) { r = udev_enumerate_add_match_subsystem(e, "input"); if (r < 0) return r; } if (c->scan_drm) { r = udev_enumerate_add_match_subsystem(e, "drm"); if (r < 0) return r; } r = udev_enumerate_add_match_is_initialized(e); if (r < 0) return r; return 0; } static int context_ud_hotplug(sysview_context *c, struct udev_device *d) { const char *syspath, *sysname, *subsystem, *action, *seatname; sysview_device *device; int r; syspath = udev_device_get_syspath(d); sysname = udev_device_get_sysname(d); subsystem = udev_device_get_subsystem(d); action = udev_device_get_action(d); /* not interested in custom devices without syspath/etc */ if (!syspath || !sysname || !subsystem) return 0; device = sysview_find_device(c, syspath); if (streq_ptr(action, "remove")) { if (!device) return 0; return context_remove_device(c, device); } else if (streq_ptr(action, "change")) { if (!device) return 0; /* TODO: send REFRESH event */ } else if (!action || streq_ptr(action, "add")) { struct udev_device *p; unsigned int type, t; sysview_seat *seat; if (device) return 0; if (streq(subsystem, "input") && startswith(sysname, "event") && safe_atou(sysname + 5, &t) >= 0) type = SYSVIEW_DEVICE_EVDEV; else if (streq(subsystem, "drm") && startswith(sysname, "card") && safe_atou(sysname + 4, &t) >= 0) type = SYSVIEW_DEVICE_DRM; else type = (unsigned)-1; if (type >= SYSVIEW_DEVICE_CNT) return 0; p = d; seatname = NULL; while ((p = udev_device_get_parent(p))) { seatname = udev_device_get_property_value(p, "ID_SEAT"); if (seatname) break; } seat = sysview_find_seat(c, seatname ? : "seat0"); if (!seat) return 0; r = device_new_ud(&device, seat, type, d); if (r < 0) { log_debug("sysview: cannot create device for udev-device '%s': %s", syspath, strerror(-r)); return r; } return context_add_device(c, device); } return 0; } static int context_ud_monitor_fn(sd_event_source *s, int fd, uint32_t revents, void *userdata) { sysview_context *c = userdata; struct udev_device *d; int r; if (revents & EPOLLIN) { while ((d = udev_monitor_receive_device(c->ud_monitor))) { r = context_ud_hotplug(c, d); udev_device_unref(d); if (r != 0) return r; } /* as long as EPOLLIN is signalled, read pending data */ return 0; } if (revents & (EPOLLHUP | EPOLLERR)) { log_debug("sysview: HUP on udev-monitor"); c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src); } return 0; } static int context_ud_start(sysview_context *c) { int r, fd; if (!c->ud) return 0; errno = 0; c->ud_monitor = udev_monitor_new_from_netlink(c->ud, "udev"); if (!c->ud_monitor) return errno > 0 ? -errno : -EFAULT; r = context_ud_prepare_monitor(c, c->ud_monitor); if (r < 0) return r; r = udev_monitor_enable_receiving(c->ud_monitor); if (r < 0) return r; fd = udev_monitor_get_fd(c->ud_monitor); r = sd_event_add_io(c->event, &c->ud_monitor_src, fd, EPOLLHUP | EPOLLERR | EPOLLIN, context_ud_monitor_fn, c); if (r < 0) return r; return 0; } static void context_ud_stop(sysview_context *c) { c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src); c->ud_monitor = udev_monitor_unref(c->ud_monitor); } static int context_ud_scan(sysview_context *c) { _cleanup_(udev_enumerate_unrefp) struct udev_enumerate *e = NULL; struct udev_list_entry *entry; struct udev_device *d; int r; if (!c->ud_monitor) return 0; errno = 0; e = udev_enumerate_new(c->ud); if (!e) return errno > 0 ? -errno : -EFAULT; r = context_ud_prepare_scan(c, e); if (r < 0) return r; r = udev_enumerate_scan_devices(e); if (r < 0) return r; udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { const char *name; name = udev_list_entry_get_name(entry); errno = 0; d = udev_device_new_from_syspath(c->ud, name); if (!d) { r = errno > 0 ? -errno : -EFAULT; log_debug("sysview: cannot create udev-device for %s: %s", name, strerror(-r)); continue; } r = context_ud_hotplug(c, d); udev_device_unref(d); if (r != 0) return r; } return 0; } static int context_ld_seat_new(sysview_context *c, sd_bus_message *signal) { const char *id, *path; int r; r = sd_bus_message_read(signal, "so", &id, &path); if (r < 0) { log_debug("sysview: cannot parse SeatNew from logind: %s", strerror(-r)); return r; } return context_add_seat(c, id); } static int context_ld_seat_removed(sysview_context *c, sd_bus_message *signal) { const char *id, *path; sysview_seat *seat; int r; r = sd_bus_message_read(signal, "so", &id, &path); if (r < 0) { log_debug("sysview: cannot parse SeatRemoved from logind: %s", strerror(-r)); return r; } seat = sysview_find_seat(c, id); if (!seat) return 0; return context_remove_seat(c, seat); } static int context_ld_session_new(sysview_context *c, sd_bus_message *signal) { _cleanup_free_ char *seatid = NULL, *username = NULL; const char *id, *path; sysview_seat *seat; uid_t uid; int r; r = sd_bus_message_read(signal, "so", &id, &path); if (r < 0) { log_debug("sysview: cannot parse SessionNew from logind: %s", strerror(-r)); return r; } /* * As the dbus message didn't contain enough information, we * read missing bits via sd-login. Note that this might race session * destruction, so we handle ENOENT properly. */ /* ENOENT is also returned for sessions without seats */ r = sd_session_get_seat(id, &seatid); if (r == -ENOENT) return 0; else if (r < 0) goto error; seat = sysview_find_seat(c, seatid); if (!seat) return 0; r = sd_session_get_uid(id, &uid); if (r == -ENOENT) return 0; else if (r < 0) goto error; username = lookup_uid(uid); if (!username) { r = -ENOMEM; goto error; } r = context_raise_session_filter(c, id, seatid, username, uid); if (r <= 0) { if (r < 0) log_debug("sysview: cannot filter new session '%s' on seat '%s': %s", id, seatid, strerror(-r)); return r; } return context_add_session(c, seat, id); error: log_debug("sysview: failed retrieving information for new session '%s': %s", id, strerror(-r)); return r; } static int context_ld_session_removed(sysview_context *c, sd_bus_message *signal) { sysview_session *session; const char *id, *path; int r; r = sd_bus_message_read(signal, "so", &id, &path); if (r < 0) { log_debug("sysview: cannot parse SessionRemoved from logind: %s", strerror(-r)); return r; } session = sysview_find_session(c, id); if (!session) return 0; return context_remove_session(c, session); } static int context_ld_manager_signal_fn(sd_bus *bus, sd_bus_message *signal, void *userdata, sd_bus_error *ret_error) { sysview_context *c = userdata; if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatNew")) return context_ld_seat_new(c, signal); else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatRemoved")) return context_ld_seat_removed(c, signal); else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionNew")) return context_ld_session_new(c, signal); else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionRemoved")) return context_ld_session_removed(c, signal); else return 0; } static int context_ld_start(sysview_context *c) { int r; if (!c->scan_logind) return 0; r = sd_bus_add_match(c->sysbus, &c->ld_slot_manager_signal, "type='signal'," "sender='org.freedesktop.login1'," "interface='org.freedesktop.login1.Manager'," "path='/org/freedesktop/login1'", context_ld_manager_signal_fn, c); if (r < 0) return r; return 0; } static void context_ld_stop(sysview_context *c) { c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions); c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats); c->ld_slot_manager_signal = sd_bus_slot_unref(c->ld_slot_manager_signal); } static int context_ld_list_seats_fn(sd_bus *bus, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { sysview_context *c = userdata; int r; c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats); if (sd_bus_message_is_method_error(reply, NULL)) { const sd_bus_error *error = sd_bus_message_get_error(reply); log_debug("sysview: ListSeats on logind failed: %s: %s", error->name, error->message); return sd_bus_error_get_errno(error); } r = sd_bus_message_enter_container(reply, 'a', "(so)"); if (r < 0) goto error; while ((r = sd_bus_message_enter_container(reply, 'r', "so")) > 0) { const char *id, *path; r = sd_bus_message_read(reply, "so", &id, &path); if (r < 0) goto error; r = context_add_seat(c, id); if (r != 0) return r; r = sd_bus_message_exit_container(reply); if (r < 0) goto error; } if (r < 0) goto error; r = sd_bus_message_exit_container(reply); if (r < 0) return r; return 0; error: log_debug("sysview: erroneous ListSeats response from logind: %s", strerror(-r)); return r; } static int context_ld_list_sessions_fn(sd_bus *bus, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { sysview_context *c = userdata; int r; c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions); if (sd_bus_message_is_method_error(reply, NULL)) { const sd_bus_error *error = sd_bus_message_get_error(reply); log_debug("sysview: ListSessions on logind failed: %s: %s", error->name, error->message); return sd_bus_error_get_errno(error); } r = sd_bus_message_enter_container(reply, 'a', "(susso)"); if (r < 0) goto error; while ((r = sd_bus_message_enter_container(reply, 'r', "susso")) > 0) { const char *id, *username, *seatid, *path; sysview_seat *seat; unsigned int uid; r = sd_bus_message_read(reply, "susso", &id, &uid, &username, &seatid, &path); if (r < 0) goto error; seat = sysview_find_seat(c, seatid); if (seat) { r = context_raise_session_filter(c, id, seatid, username, uid); if (r < 0) { log_debug("sysview: cannot filter listed session '%s' on seat '%s': %s", id, seatid, strerror(-r)); return r; } else if (r > 0) { r = context_add_session(c, seat, id); if (r != 0) return r; } } r = sd_bus_message_exit_container(reply); if (r < 0) goto error; } if (r < 0) goto error; r = sd_bus_message_exit_container(reply); if (r < 0) return r; return 0; error: log_debug("sysview: erroneous ListSessions response from logind: %s", strerror(-r)); return r; } static int context_ld_scan(sysview_context *c) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; int r; if (!c->ld_slot_manager_signal) return 0; /* request seat list */ r = sd_bus_message_new_method_call(c->sysbus, &m, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "ListSeats"); if (r < 0) return r; r = sd_bus_call_async(c->sysbus, &c->ld_slot_list_seats, m, context_ld_list_seats_fn, c, 0); if (r < 0) return r; /* request session list */ m = sd_bus_message_unref(m); r = sd_bus_message_new_method_call(c->sysbus, &m, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "ListSessions"); if (r < 0) return r; r = sd_bus_call_async(c->sysbus, &c->ld_slot_list_sessions, m, context_ld_list_sessions_fn, c, 0); if (r < 0) return r; return 0; } bool sysview_context_is_running(sysview_context *c) { return c && c->running; } int sysview_context_start(sysview_context *c, sysview_event_fn event_fn, void *userdata) { int r; assert_return(c, -EINVAL); assert_return(event_fn, -EINVAL); if (c->running) return -EALREADY; log_debug("sysview: start"); c->running = true; c->event_fn = event_fn; c->userdata = userdata; r = context_ld_start(c); if (r < 0) goto error; r = context_ud_start(c); if (r < 0) goto error; r = sysview_context_rescan(c); if (r < 0) goto error; return 0; error: sysview_context_stop(c); return r; } void sysview_context_stop(sysview_context *c) { sysview_session *session; sysview_device *device; sysview_seat *seat; assert(c); if (!c->running) return; log_debug("sysview: stop"); c->running = false; c->scanned = false; c->event_fn = NULL; c->userdata = NULL; c->scan_src = sd_event_source_unref(c->scan_src); context_ud_stop(c); context_ld_stop(c); /* * Event-callbacks are already cleared, hence we can safely ignore * return codes of the context_remove_*() helpers. They cannot be * originated from user-callbacks, so we already handled them. */ while ((device = hashmap_first(c->device_map))) context_remove_device(c, device); while ((session = hashmap_first(c->session_map))) context_remove_session(c, session); while ((seat = hashmap_first(c->seat_map))) context_remove_seat(c, seat); } static int context_scan_fn(sd_event_source *s, void *userdata) { sysview_context *c = userdata; sysview_seat *seat; Iterator i; int r; if (!c->scanned) { r = context_ld_scan(c); if (r < 0) { log_debug("sysview: logind scan failed: %s", strerror(-r)); return r; } } /* skip device scans if no sessions are available */ if (hashmap_size(c->session_map) > 0) { r = context_ud_scan(c); if (r < 0) { log_debug("sysview: udev scan failed: %s", strerror(-r)); return r; } HASHMAP_FOREACH(seat, c->seat_map, i) seat->scanned = true; } c->scanned = true; return 0; } int sysview_context_rescan(sysview_context *c) { assert(c); if (!c->running) return 0; if (c->scan_src) return sd_event_source_set_enabled(c->scan_src, SD_EVENT_ONESHOT); else return sd_event_add_defer(c->event, &c->scan_src, context_scan_fn, c); }