Systemd/src/libsystemd-terminal/sysview.c
David Herrmann b2068772f5 terminal/sysview: introduce SETTLE events
This introduces a new SYSVIEW_EVENT_SETTLE notification that is sent after
initial scanning via sysview is done. This is very handy to let the
application raise warnings in case requested resources are not found
during startup.

The SETTLE event is sent after systemd-logind and udev enumerations are
done. This event does in no way guarantee that a given resource is
available. All it does is notify the application that scanning is done!
You must not react to SETTLE if you don't have external synchronization
with the resource you're waiting for.

The main use-case for SETTLE is to run applications _inside_ of logind
sessions and startup sysview. You really want to make sure that the own
session you're running in was found during enumeration. If not, something
is seriously wrong.
2015-03-11 12:32:48 +01:00

1559 lines
46 KiB
C

/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
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 <http://www.gnu.org/licenses/>.
***/
#include <inttypes.h>
#include <libudev.h>
#include <stdbool.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>
#include <systemd/sd-login.h>
#include "bus-util.h"
#include "macro.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->name);
free(device);
return NULL;
}
const char *sysview_device_get_name(sysview_device *device) {
assert_return(device, NULL);
return device->name;
}
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;
}
void sysview_session_set_userdata(sysview_session *session, void *userdata) {
assert(session);
session->userdata = userdata;
}
void *sysview_session_get_userdata(sysview_session *session) {
assert_return(session, NULL);
return session->userdata;
}
const char *sysview_session_get_name(sysview_session *session) {
assert_return(session, NULL);
return session->name;
}
sysview_seat *sysview_session_get_seat(sysview_session *session) {
assert_return(session, NULL);
return session->seat;
}
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 r, 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;
}
r = context_raise_session_control(session->seat->context, session, error);
if (r < 0)
log_debug_errno(r, "sysview: callback failed while signalling session control '%d' on session '%s': %m",
error, session->name);
return 0;
}
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_errno(r, "sysview: %s: cannot send ReleaseControl: %m",
session->name);
}
/*
* 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;
r = sd_bus_path_encode("/org/freedesktop/login1/seat", seat->name, &seat->path);
if (r < 0)
return r;
seat->session_map = hashmap_new(&string_hash_ops);
if (!seat->session_map)
return -ENOMEM;
seat->device_map = hashmap_new(&string_hash_ops);
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->path);
free(seat->name);
free(seat);
return NULL;
}
const char *sysview_seat_get_name(sysview_seat *seat) {
assert_return(seat, NULL);
return seat->name;
}
int sysview_seat_switch_to(sysview_seat *seat, uint32_t nr) {
_cleanup_bus_message_unref_ sd_bus_message *m = NULL;
int r;
assert_return(seat, -EINVAL);
assert_return(seat->context->sysbus, -EINVAL);
r = sd_bus_message_new_method_call(seat->context->sysbus,
&m,
"org.freedesktop.login1",
seat->path,
"org.freedesktop.login1.Seat",
"SwitchTo");
if (r < 0)
return r;
r = sd_bus_message_append(m, "u", nr);
if (r < 0)
return r;
return sd_bus_send(seat->context->sysbus, m, NULL);
}
/*
* 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_settle(sysview_context *c) {
sysview_event event = {
.type = SYSVIEW_EVENT_SETTLE,
};
return context_raise(c, &event, 0);
}
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_raise_session_refresh(sysview_context *c, sysview_session *session, sysview_device *device, struct udev_device *ud) {
sysview_event event = {
.type = SYSVIEW_EVENT_SESSION_REFRESH,
.session_refresh = {
.session = session,
.device = device,
.ud = ud,
}
};
return context_raise(c, &event, 0);
}
static void context_settle(sysview_context *c) {
int r;
if (c->n_probe <= 0 || --c->n_probe > 0)
return;
log_debug("sysview: settle");
c->settled = true;
r = context_raise_settle(c);
if (r < 0)
log_debug_errno(r, "sysview: callback failed on settle: %m");
}
static void context_add_device(sysview_context *c, sysview_device *device) {
sysview_session *session;
Iterator i;
int r;
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)
log_debug_errno(r, "sysview: callback failed while attaching device '%s' to session '%s': %m",
device->name, session->name);
}
}
static void context_remove_device(sysview_context *c, sysview_device *device) {
sysview_session *session;
Iterator i;
int r;
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)
log_debug_errno(r, "sysview: callback failed while detaching device '%s' from session '%s': %m",
device->name, session->name);
}
sysview_device_free(device);
}
static void context_change_device(sysview_context *c, sysview_device *device, struct udev_device *ud) {
sysview_session *session;
Iterator i;
int r;
assert(c);
assert(device);
log_debug("sysview: change device '%s'", device->name);
HASHMAP_FOREACH(session, device->seat->session_map, i) {
if (!session->public)
continue;
r = context_raise_session_refresh(c, session, device, ud);
if (r < 0)
log_debug_errno(r, "sysview: callback failed while changing device '%s' on session '%s': %m",
device->name, session->name);
}
}
static void context_add_session(sysview_context *c, sysview_seat *seat, const char *id) {
sysview_session *session;
sysview_device *device;
Iterator i;
int r;
assert(c);
assert(seat);
assert(id);
session = sysview_find_session(c, id);
if (session)
return;
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) {
log_debug_errno(r, "sysview: callback failed while adding session '%s': %m",
session->name);
session->public = false;
goto error;
}
HASHMAP_FOREACH(device, seat->device_map, i) {
r = context_raise_session_attach(c, session, device);
if (r < 0)
log_debug_errno(r, "sysview: callback failed while attaching device '%s' to new session '%s': %m",
device->name, session->name);
}
}
return;
error:
if (r < 0)
log_debug_errno(r, "sysview: error while adding session '%s': %m",
id);
}
static void context_remove_session(sysview_context *c, sysview_session *session) {
sysview_device *device;
Iterator i;
int r;
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)
log_debug_errno(r, "sysview: callback failed while detaching device '%s' from old session '%s': %m",
device->name, session->name);
}
session->public = false;
r = context_raise_session_remove(c, session);
if (r < 0)
log_debug_errno(r, "sysview: callback failed while removing session '%s': %m",
session->name);
}
if (!session->custom)
sysview_session_release_control(session);
sysview_session_free(session);
}
static void 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;
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) {
log_debug_errno(r, "sysview: callback failed while adding seat '%s': %m",
seat->name);
seat->public = false;
}
return;
error:
if (r < 0)
log_debug_errno(r, "sysview: error while adding seat '%s': %m",
id);
}
static void context_remove_seat(sysview_context *c, sysview_seat *seat) {
sysview_session *session;
sysview_device *device;
int r;
assert(c);
assert(seat);
log_debug("sysview: remove seat '%s'", seat->name);
while ((device = hashmap_first(seat->device_map)))
context_remove_device(c, device);
while ((session = hashmap_first(seat->session_map)))
context_remove_session(c, session);
if (seat->public) {
seat->public = false;
r = context_raise_seat_remove(c, seat);
if (r < 0)
log_debug_errno(r, "sysview: callback failed while removing seat '%s': %m",
seat->name);
}
sysview_seat_free(seat);
}
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_ops);
if (!c->seat_map)
return -ENOMEM;
c->session_map = hashmap_new(&string_hash_ops);
if (!c->session_map)
return -ENOMEM;
c->device_map = hashmap_new(&string_hash_ops);
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 0;
}
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;
context_remove_device(c, device);
} else if (streq_ptr(action, "change")) {
if (!device)
return 0;
context_change_device(c, device, d);
} 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"))
type = SYSVIEW_DEVICE_DRM;
else
type = (unsigned)-1;
if (type >= SYSVIEW_DEVICE_CNT)
return 0;
p = d;
seatname = NULL;
do {
seatname = udev_device_get_property_value(p, "ID_SEAT");
if (seatname)
break;
} while ((p = udev_device_get_parent(p)));
seat = sysview_find_seat(c, seatname ? : "seat0");
if (!seat)
return 0;
r = device_new_ud(&device, seat, type, d);
if (r < 0)
return log_debug_errno(r, "sysview: cannot create device for udev-device '%s': %m",
syspath);
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_errno(r, "sysview: cannot create udev-device for %s: %m",
name);
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)
return log_debug_errno(r, "sysview: cannot parse SeatNew from logind: %m");
context_add_seat(c, id);
return 0;
}
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)
return log_debug_errno(r, "sysview: cannot parse SeatRemoved from logind: %m");
seat = sysview_find_seat(c, id);
if (!seat)
return 0;
context_remove_seat(c, seat);
return 0;
}
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)
return log_debug_errno(r, "sysview: cannot parse SessionNew from logind: %m");
/*
* 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)
log_debug_errno(r, "sysview: callback failed while filtering session '%s': %m",
id);
else if (r > 0)
context_add_session(c, seat, id);
return 0;
error:
return log_debug_errno(r, "sysview: failed retrieving information for new session '%s': %m",
id);
}
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)
return log_debug_errno(r, "sysview: cannot parse SessionRemoved from logind: %m");
session = sysview_find_session(c, id);
if (!session)
return 0;
context_remove_session(c, session);
return 0;
}
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);
r = -sd_bus_error_get_errno(error);
goto settle;
}
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;
context_add_seat(c, id);
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)
goto error;
r = 0;
goto settle;
error:
log_debug_errno(r, "sysview: erroneous ListSeats response from logind: %m");
settle:
context_settle(c);
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);
r = -sd_bus_error_get_errno(error);
goto settle;
}
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_errno(r, "sysview: callback failed while filtering session '%s': %m",
id);
else if (r > 0)
context_add_session(c, seat, id);
}
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)
goto error;
r = 0;
goto settle;
error:
log_debug_errno(r, "sysview: erroneous ListSessions response from logind: %m");
settle:
context_settle(c);
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;
if (!c->settled)
++c->n_probe;
/* 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;
if (!c->settled)
++c->n_probe;
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");
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);
c->running = false;
c->scanned = false;
c->settled = false;
c->n_probe = 0;
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);
}
static int context_scan_fn(sd_event_source *s, void *userdata) {
sysview_context *c = userdata;
sysview_seat *seat;
Iterator i;
int r;
c->rescan = false;
if (!c->scanned) {
r = context_ld_scan(c);
if (r < 0)
return log_debug_errno(r, "sysview: logind scan failed: %m");
}
/* skip device scans if no sessions are available */
if (hashmap_size(c->session_map) > 0) {
r = context_ud_scan(c);
if (r < 0)
return log_debug_errno(r, "sysview: udev scan failed: %m");
HASHMAP_FOREACH(seat, c->seat_map, i)
seat->scanned = true;
}
c->scanned = true;
context_settle(c);
return 0;
}
int sysview_context_rescan(sysview_context *c) {
assert(c);
if (!c->running)
return 0;
if (!c->rescan) {
c->rescan = true;
if (!c->settled)
++c->n_probe;
}
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);
}