diff options
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | src/libsystemd-terminal/idev-internal.h | 165 | ||||
-rw-r--r-- | src/libsystemd-terminal/idev.c | 587 | ||||
-rw-r--r-- | src/libsystemd-terminal/idev.h | 133 |
4 files changed, 888 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index 3a263f8c17..82f474e20e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2971,6 +2971,9 @@ libsystemd_terminal_la_CFLAGS = \ $(AM_CFLAGS) libsystemd_terminal_la_SOURCES = \ + src/libsystemd-terminal/idev.h \ + src/libsystemd-terminal/idev-internal.h \ + src/libsystemd-terminal/idev.c \ src/libsystemd-terminal/sysview.h \ src/libsystemd-terminal/sysview-internal.h \ src/libsystemd-terminal/sysview.c \ diff --git a/src/libsystemd-terminal/idev-internal.h b/src/libsystemd-terminal/idev-internal.h new file mode 100644 index 0000000000..bffefbb9c1 --- /dev/null +++ b/src/libsystemd-terminal/idev-internal.h @@ -0,0 +1,165 @@ +/*-*- 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/>. +***/ + +#pragma once + +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <systemd/sd-bus.h> +#include <systemd/sd-event.h> +#include "hashmap.h" +#include "idev.h" +#include "list.h" +#include "util.h" + +typedef struct idev_link idev_link; +typedef struct idev_device_vtable idev_device_vtable; +typedef struct idev_element idev_element; +typedef struct idev_element_vtable idev_element_vtable; + +/* + * Element Links + */ + +struct idev_link { + /* element-to-device connection */ + LIST_FIELDS(idev_link, links_by_element); + idev_element *element; + + /* device-to-element connection */ + LIST_FIELDS(idev_link, links_by_device); + idev_device *device; +}; + +/* + * Devices + */ + +struct idev_device_vtable { + void (*free) (idev_device *d); + void (*attach) (idev_device *d, idev_link *l); + void (*detach) (idev_device *d, idev_link *l); + int (*feed) (idev_device *d, idev_data *data); +}; + +struct idev_device { + const idev_device_vtable *vtable; + idev_session *session; + char *name; + + LIST_HEAD(idev_link, links); + + bool public : 1; + bool enabled : 1; +}; + +#define IDEV_DEVICE_INIT(_vtable, _session) ((idev_device){ \ + .vtable = (_vtable), \ + .session = (_session), \ + }) + +idev_device *idev_find_device(idev_session *s, const char *name); + +int idev_device_add(idev_device *d, const char *name); +idev_device *idev_device_free(idev_device *d); + +DEFINE_TRIVIAL_CLEANUP_FUNC(idev_device*, idev_device_free); + +int idev_device_feed(idev_device *d, idev_data *data); +void idev_device_feedback(idev_device *d, idev_data *data); + +/* + * Elements + */ + +struct idev_element_vtable { + void (*free) (idev_element *e); + void (*enable) (idev_element *e); + void (*disable) (idev_element *e); + void (*open) (idev_element *e); + void (*close) (idev_element *e); + void (*feedback) (idev_element *e, idev_data *data); +}; + +struct idev_element { + const idev_element_vtable *vtable; + idev_session *session; + unsigned long n_open; + char *name; + + LIST_HEAD(idev_link, links); + + bool enabled : 1; + bool readable : 1; + bool writable : 1; +}; + +#define IDEV_ELEMENT_INIT(_vtable, _session) ((idev_element){ \ + .vtable = (_vtable), \ + .session = (_session), \ + }) + +idev_element *idev_find_element(idev_session *s, const char *name); + +int idev_element_add(idev_element *e, const char *name); +idev_element *idev_element_free(idev_element *e); + +DEFINE_TRIVIAL_CLEANUP_FUNC(idev_element*, idev_element_free); + +int idev_element_feed(idev_element *e, idev_data *data); +void idev_element_feedback(idev_element *e, idev_data *data); + +/* + * Sessions + */ + +struct idev_session { + idev_context *context; + char *name; + char *path; + + Hashmap *element_map; + Hashmap *device_map; + + idev_event_fn event_fn; + void *userdata; + + bool custom : 1; + bool managed : 1; + bool enabled : 1; +}; + +idev_session *idev_find_session(idev_context *c, const char *name); +int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data); + +/* + * Contexts + */ + +struct idev_context { + unsigned long ref; + sd_event *event; + sd_bus *sysbus; + + Hashmap *session_map; + Hashmap *data_map; +}; diff --git a/src/libsystemd-terminal/idev.c b/src/libsystemd-terminal/idev.c new file mode 100644 index 0000000000..5e3080797a --- /dev/null +++ b/src/libsystemd-terminal/idev.c @@ -0,0 +1,587 @@ +/*-*- 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 <stdbool.h> +#include <stdlib.h> +#include <systemd/sd-bus.h> +#include <systemd/sd-event.h> +#include <systemd/sd-login.h> +#include "hashmap.h" +#include "idev.h" +#include "idev-internal.h" +#include "login-shared.h" +#include "macro.h" +#include "set.h" +#include "util.h" + +static void element_open(idev_element *e); +static void element_close(idev_element *e); + +/* + * Devices + */ + +idev_device *idev_find_device(idev_session *s, const char *name) { + assert_return(s, NULL); + assert_return(name, NULL); + + return hashmap_get(s->device_map, name); +} + +int idev_device_add(idev_device *d, const char *name) { + int r; + + assert_return(d, -EINVAL); + assert_return(d->vtable, -EINVAL); + assert_return(d->session, -EINVAL); + assert_return(name, -EINVAL); + + d->name = strdup(name); + if (!d->name) + return -ENOMEM; + + r = hashmap_put(d->session->device_map, d->name, d); + if (r < 0) + return r; + + return 0; +} + +idev_device *idev_device_free(idev_device *d) { + idev_device tmp; + + if (!d) + return NULL; + + assert(!d->enabled); + assert(!d->public); + assert(!d->links); + assert(d->vtable); + assert(d->vtable->free); + + if (d->name) + hashmap_remove_value(d->session->device_map, d->name, d); + + tmp = *d; + d->vtable->free(d); + + free(tmp.name); + + return NULL; +} + +int idev_device_feed(idev_device *d, idev_data *data) { + assert(d); + assert(data); + assert(data->type < IDEV_DATA_CNT); + + if (d->vtable->feed) + return d->vtable->feed(d, data); + else + return 0; +} + +void idev_device_feedback(idev_device *d, idev_data *data) { + idev_link *l; + + assert(d); + assert(data); + assert(data->type < IDEV_DATA_CNT); + + LIST_FOREACH(links_by_device, l, d->links) + idev_element_feedback(l->element, data); +} + +static void device_attach(idev_device *d, idev_link *l) { + assert(d); + assert(l); + + if (d->vtable->attach) + d->vtable->attach(d, l); + + if (d->enabled) + element_open(l->element); +} + +static void device_detach(idev_device *d, idev_link *l) { + assert(d); + assert(l); + + if (d->enabled) + element_close(l->element); + + if (d->vtable->detach) + d->vtable->detach(d, l); +} + +void idev_device_enable(idev_device *d) { + idev_link *l; + + assert(d); + + if (!d->enabled) { + d->enabled = true; + LIST_FOREACH(links_by_device, l, d->links) + element_open(l->element); + } +} + +void idev_device_disable(idev_device *d) { + idev_link *l; + + assert(d); + + if (d->enabled) { + d->enabled = false; + LIST_FOREACH(links_by_device, l, d->links) + element_close(l->element); + } +} + +/* + * Elements + */ + +idev_element *idev_find_element(idev_session *s, const char *name) { + assert_return(s, NULL); + assert_return(name, NULL); + + return hashmap_get(s->element_map, name); +} + +int idev_element_add(idev_element *e, const char *name) { + int r; + + assert_return(e, -EINVAL); + assert_return(e->vtable, -EINVAL); + assert_return(e->session, -EINVAL); + assert_return(name, -EINVAL); + + e->name = strdup(name); + if (!e->name) + return -ENOMEM; + + r = hashmap_put(e->session->element_map, e->name, e); + if (r < 0) + return r; + + return 0; +} + +idev_element *idev_element_free(idev_element *e) { + idev_element tmp; + + if (!e) + return NULL; + + assert(!e->enabled); + assert(!e->links); + assert(e->n_open == 0); + assert(e->vtable); + assert(e->vtable->free); + + if (e->name) + hashmap_remove_value(e->session->element_map, e->name, e); + + tmp = *e; + e->vtable->free(e); + + free(tmp.name); + + return NULL; +} + +int idev_element_feed(idev_element *e, idev_data *data) { + int r, error = 0; + idev_link *l; + + assert(e); + assert(data); + assert(data->type < IDEV_DATA_CNT); + + LIST_FOREACH(links_by_element, l, e->links) { + r = idev_device_feed(l->device, data); + if (r != 0) + error = r; + } + + return error; +} + +void idev_element_feedback(idev_element *e, idev_data *data) { + assert(e); + assert(data); + assert(data->type < IDEV_DATA_CNT); + + if (e->vtable->feedback) + e->vtable->feedback(e, data); +} + +static void element_open(idev_element *e) { + assert(e); + + if (e->n_open++ == 0 && e->vtable->open) + e->vtable->open(e); +} + +static void element_close(idev_element *e) { + assert(e); + assert(e->n_open > 0); + + if (--e->n_open == 0 && e->vtable->close) + e->vtable->close(e); +} + +static void element_enable(idev_element *e) { + assert(e); + + if (!e->enabled) { + e->enabled = true; + if (e->vtable->enable) + e->vtable->enable(e); + } +} + +static void element_disable(idev_element *e) { + assert(e); + + if (e->enabled) { + e->enabled = false; + if (e->vtable->disable) + e->vtable->disable(e); + } +} + +/* + * Sessions + */ + +static int session_raise(idev_session *s, idev_event *ev) { + return s->event_fn(s, s->userdata, ev); +} + +static int session_raise_device_add(idev_session *s, idev_device *d) { + idev_event event = { + .type = IDEV_EVENT_DEVICE_ADD, + .device_add = { + .device = d, + }, + }; + + return session_raise(s, &event); +} + +static int session_raise_device_remove(idev_session *s, idev_device *d) { + idev_event event = { + .type = IDEV_EVENT_DEVICE_REMOVE, + .device_remove = { + .device = d, + }, + }; + + return session_raise(s, &event); +} + +int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) { + idev_event event = { + .type = IDEV_EVENT_DEVICE_DATA, + .device_data = { + .device = d, + .data = *data, + }, + }; + + return session_raise(s, &event); +} + +static int session_add_device(idev_session *s, idev_device *d) { + int r; + + assert(s); + assert(d); + + log_debug("idev: %s: add device '%s'", s->name, d->name); + + d->public = true; + r = session_raise_device_add(s, d); + if (r != 0) { + d->public = false; + goto error; + } + + return 0; + +error: + if (r < 0) + log_debug("idev: %s: error while adding device '%s': %s", + s->name, d->name, strerror(-r)); + return r; +} + +static int session_remove_device(idev_session *s, idev_device *d) { + int r, error = 0; + + assert(s); + assert(d); + + log_debug("idev: %s: remove device '%s'", s->name, d->name); + + d->public = false; + r = session_raise_device_remove(s, d); + if (r != 0) + error = r; + + idev_device_disable(d); + + if (error < 0) + log_debug("idev: %s: error while removing device '%s': %s", + s->name, d->name, strerror(-error)); + idev_device_free(d); + return error; +} + +static int session_add_element(idev_session *s, idev_element *e) { + assert(s); + assert(e); + + log_debug("idev: %s: add element '%s'", s->name, e->name); + + if (s->enabled) + element_enable(e); + + return 0; +} + +static int session_remove_element(idev_session *s, idev_element *e) { + int r, error = 0; + idev_device *d; + idev_link *l; + + assert(s); + assert(e); + + log_debug("idev: %s: remove element '%s'", s->name, e->name); + + while ((l = e->links)) { + d = l->device; + LIST_REMOVE(links_by_device, d->links, l); + LIST_REMOVE(links_by_element, e->links, l); + device_detach(d, l); + + if (!d->links) { + r = session_remove_device(s, d); + if (r != 0) + error = r; + } + + l->device = NULL; + l->element = NULL; + free(l); + } + + element_disable(e); + + if (error < 0) + log_debug("idev: %s: error while removing element '%s': %s", + s->name, e->name, strerror(-r)); + idev_element_free(e); + return error; +} + +idev_session *idev_find_session(idev_context *c, const char *name) { + assert_return(c, NULL); + assert_return(name, NULL); + + return hashmap_get(c->session_map, name); +} + +int idev_session_new(idev_session **out, + idev_context *c, + unsigned int flags, + const char *name, + idev_event_fn event_fn, + void *userdata) { + _cleanup_(idev_session_freep) idev_session *s = NULL; + int r; + + assert_return(out, -EINVAL); + assert_return(c, -EINVAL); + assert_return(name, -EINVAL); + assert_return(event_fn, -EINVAL); + assert_return((flags & IDEV_SESSION_CUSTOM) == !session_id_valid(name), -EINVAL); + assert_return(!(flags & IDEV_SESSION_CUSTOM) || !(flags & IDEV_SESSION_MANAGED), -EINVAL); + assert_return(!(flags & IDEV_SESSION_MANAGED) || c->sysbus, -EINVAL); + + s = new0(idev_session, 1); + if (!s) + return -ENOMEM; + + s->context = idev_context_ref(c); + s->custom = flags & IDEV_SESSION_CUSTOM; + s->managed = flags & IDEV_SESSION_MANAGED; + s->event_fn = event_fn; + s->userdata = userdata; + + s->name = strdup(name); + if (!s->name) + return -ENOMEM; + + if (s->managed) { + r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path); + if (r < 0) + return r; + } + + s->element_map = hashmap_new(string_hash_func, string_compare_func); + if (!s->element_map) + return -ENOMEM; + + s->device_map = hashmap_new(string_hash_func, string_compare_func); + if (!s->device_map) + return -ENOMEM; + + r = hashmap_put(c->session_map, s->name, s); + if (r < 0) + return r; + + *out = s; + s = NULL; + return 0; +} + +idev_session *idev_session_free(idev_session *s) { + idev_element *e; + + if (!s) + return NULL; + + while ((e = hashmap_first(s->element_map))) + session_remove_element(s, e); + + assert(hashmap_size(s->device_map) == 0); + + if (s->name) + hashmap_remove_value(s->context->session_map, s->name, s); + + s->context = idev_context_unref(s->context); + hashmap_free(s->device_map); + hashmap_free(s->element_map); + free(s->path); + free(s->name); + free(s); + + return NULL; +} + +bool idev_session_is_enabled(idev_session *s) { + return s && s->enabled; +} + +void idev_session_enable(idev_session *s) { + idev_element *e; + Iterator i; + + assert(s); + + if (!s->enabled) { + s->enabled = true; + HASHMAP_FOREACH(e, s->element_map, i) + element_enable(e); + } +} + +void idev_session_disable(idev_session *s) { + idev_element *e; + Iterator i; + + assert(s); + + if (s->enabled) { + s->enabled = false; + HASHMAP_FOREACH(e, s->element_map, i) + element_disable(e); + } +} + +/* + * Contexts + */ + +int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) { + _cleanup_(idev_context_unrefp) idev_context *c = NULL; + + assert_return(out, -EINVAL); + assert_return(event, -EINVAL); + + c = new0(idev_context, 1); + if (!c) + return -ENOMEM; + + c->ref = 1; + c->event = sd_event_ref(event); + + if (sysbus) + c->sysbus = sd_bus_ref(sysbus); + + c->session_map = hashmap_new(string_hash_func, string_compare_func); + if (!c->session_map) + return -ENOMEM; + + c->data_map = hashmap_new(string_hash_func, string_compare_func); + if (!c->data_map) + return -ENOMEM; + + *out = c; + c = NULL; + return 0; +} + +static void context_cleanup(idev_context *c) { + assert(hashmap_size(c->data_map) == 0); + assert(hashmap_size(c->session_map) == 0); + + hashmap_free(c->data_map); + hashmap_free(c->session_map); + c->sysbus = sd_bus_unref(c->sysbus); + c->event = sd_event_unref(c->event); + free(c); +} + +idev_context *idev_context_ref(idev_context *c) { + assert_return(c, NULL); + assert_return(c->ref > 0, NULL); + + ++c->ref; + return c; +} + +idev_context *idev_context_unref(idev_context *c) { + if (!c) + return NULL; + + assert_return(c->ref > 0, NULL); + + if (--c->ref == 0) + context_cleanup(c); + + return NULL; +} diff --git a/src/libsystemd-terminal/idev.h b/src/libsystemd-terminal/idev.h new file mode 100644 index 0000000000..6f618f37af --- /dev/null +++ b/src/libsystemd-terminal/idev.h @@ -0,0 +1,133 @@ +/*-*- 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/>. +***/ + +/* + * IDev + */ + +#pragma once + +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <systemd/sd-bus.h> +#include <systemd/sd-event.h> +#include "util.h" + +typedef struct idev_data idev_data; + +typedef struct idev_event idev_event; +typedef struct idev_device idev_device; +typedef struct idev_session idev_session; +typedef struct idev_context idev_context; + +/* + * Types + */ + +enum { + IDEV_ELEMENT_CNT +}; + +enum { + IDEV_DEVICE_CNT +}; + +/* + * Data Packets + */ + +enum { + IDEV_DATA_RESYNC, + IDEV_DATA_CNT +}; + +struct idev_data { + unsigned int type; + bool resync : 1; +}; + +/* + * Events + */ + +enum { + IDEV_EVENT_DEVICE_ADD, + IDEV_EVENT_DEVICE_REMOVE, + IDEV_EVENT_DEVICE_DATA, + IDEV_EVENT_CNT +}; + +struct idev_event { + unsigned int type; + union { + struct { + idev_device *device; + } device_add, device_remove; + + struct { + idev_device *device; + idev_data data; + } device_data; + }; +}; + +typedef int (*idev_event_fn) (idev_session *s, void *userdata, idev_event *ev); + +/* + * Devices + */ + +void idev_device_enable(idev_device *d); +void idev_device_disable(idev_device *d); + +/* + * Sessions + */ + +enum { + IDEV_SESSION_CUSTOM = (1 << 0), + IDEV_SESSION_MANAGED = (1 << 1), +}; + +int idev_session_new(idev_session **out, + idev_context *c, + unsigned int flags, + const char *name, + idev_event_fn event_fn, + void *userdata); +idev_session *idev_session_free(idev_session *s); + +DEFINE_TRIVIAL_CLEANUP_FUNC(idev_session*, idev_session_free); + +bool idev_session_is_enabled(idev_session *s); +void idev_session_enable(idev_session *s); +void idev_session_disable(idev_session *s); + +/* + * Contexts + */ + +int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus); +idev_context *idev_context_ref(idev_context *c); +idev_context *idev_context_unref(idev_context *c); + +DEFINE_TRIVIAL_CLEANUP_FUNC(idev_context*, idev_context_unref); |