From be528ba7c2a8ede730d927398f9b4a6d3535ff3c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 17 Apr 2017 16:32:25 +0200 Subject: [PATCH] flatpak: add flatpak module Pass LoopUtils to callbacks to make it easier to reschedule timeouts. --- pinos/client/context.c | 14 +- pinos/client/loop.c | 63 ++- pinos/client/stream.c | 8 +- pinos/client/thread-mainloop.c | 5 +- pinos/daemon/pinos.conf.in | 2 +- pinos/modules/meson.build | 4 +- pinos/modules/module-access.c | 188 ------- pinos/modules/module-flatpak.c | 717 +++++++++++++++++++++++++ pinos/modules/module-protocol-native.c | 18 +- pinos/modules/module-suspend-on-idle.c | 5 +- pinos/server/access.h | 4 +- pinos/server/client.c | 3 + pinos/server/client.h | 3 + pinos/server/data-loop.c | 5 +- pinos/server/work-queue.c | 5 +- spa/include/spa/defs.h | 9 +- spa/include/spa/loop.h | 32 +- 17 files changed, 826 insertions(+), 259 deletions(-) delete mode 100644 pinos/modules/module-access.c create mode 100644 pinos/modules/module-flatpak.c diff --git a/pinos/client/context.c b/pinos/client/context.c index 69b2831c0..bd31ec4b0 100644 --- a/pinos/client/context.c +++ b/pinos/client/context.c @@ -376,8 +376,9 @@ static const PinosRegistryEvents registry_events = { typedef bool (*PinosDemarshalFunc) (void *object, void *data, size_t size); static void -do_flush_event (SpaSource *source, - void *data) +do_flush_event (SpaLoopUtils *utils, + SpaSource *source, + void *data) { PinosContextImpl *impl = data; if (impl->connection) @@ -394,10 +395,11 @@ on_need_flush (PinosListener *listener, } static void -on_context_data (SpaSource *source, - int fd, - SpaIO mask, - void *data) +on_context_data (SpaLoopUtils *utils, + SpaSource *source, + int fd, + SpaIO mask, + void *data) { PinosContextImpl *impl = data; PinosContext *this = &impl->this; diff --git a/pinos/client/loop.c b/pinos/client/loop.c index ee749cbcc..bee850bed 100644 --- a/pinos/client/loop.c +++ b/pinos/client/loop.c @@ -35,21 +35,6 @@ #include #include -typedef struct { - SpaSource source; - SpaList link; - - bool close; - union { - SpaSourceIOFunc io; - SpaSourceIdleFunc idle; - SpaSourceEventFunc event; - SpaSourceTimerFunc timer; - SpaSourceSignalFunc signal; - } func; - int signal_number; -} SpaSourceImpl; - #define DATAS_SIZE (4096 * 8) typedef struct { @@ -83,6 +68,24 @@ typedef struct { uint8_t buffer_data[DATAS_SIZE]; } PinosLoopImpl; +typedef struct { + SpaSource source; + + PinosLoopImpl *impl; + SpaList link; + + bool close; + union { + SpaSourceIOFunc io; + SpaSourceIdleFunc idle; + SpaSourceEventFunc event; + SpaSourceTimerFunc timer; + SpaSourceSignalFunc signal; + } func; + int signal_number; + bool enabled; +} SpaSourceImpl; + static inline uint32_t spa_io_to_epoll (SpaIO mask) { @@ -221,8 +224,9 @@ loop_invoke (SpaLoop *loop, } static void -event_func (SpaSource *source, - void *data) +event_func (SpaLoopUtils *utils, + SpaSource *source, + void *data) { PinosLoopImpl *impl = data; uint32_t offset; @@ -314,7 +318,7 @@ static void source_io_func (SpaSource *source) { SpaSourceImpl *impl = SPA_CONTAINER_OF (source, SpaSourceImpl, source); - impl->func.io (source, source->fd, source->rmask, source->data); + impl->func.io (&impl->impl->utils, source, source->fd, source->rmask, source->data); } static SpaSource * @@ -337,6 +341,7 @@ loop_add_io (SpaLoopUtils *utils, source->source.data = data; source->source.fd = fd; source->source.mask = mask; + source->impl = impl; source->close = close; source->func.io = func; @@ -360,11 +365,12 @@ static void source_idle_func (SpaSource *source) { SpaSourceImpl *impl = SPA_CONTAINER_OF (source, SpaSourceImpl, source); - impl->func.idle (source, source->data); + impl->func.idle (&impl->impl->utils, source, source->data); } static SpaSource * loop_add_idle (SpaLoopUtils *utils, + bool enabled, SpaSourceIdleFunc func, void *data) { @@ -379,6 +385,7 @@ loop_add_idle (SpaLoopUtils *utils, source->source.func = source_idle_func; source->source.data = data; source->source.fd = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK); + source->impl = impl; source->close = true; source->source.mask = SPA_IO_IN; source->func.idle = func; @@ -387,7 +394,8 @@ loop_add_idle (SpaLoopUtils *utils, spa_list_insert (&impl->source_list, &source->link); - spa_loop_utils_enable_idle (&impl->utils, &source->source, true); + if (enabled) + spa_loop_utils_enable_idle (&impl->utils, &source->source, true); return &source->source; } @@ -396,16 +404,18 @@ static void loop_enable_idle (SpaSource *source, bool enabled) { + SpaSourceImpl *impl = SPA_CONTAINER_OF (source, SpaSourceImpl, source); uint64_t count; - if (enabled) { + if (enabled && !impl->enabled) { count = 1; if (write (source->fd, &count, sizeof (uint64_t)) != sizeof (uint64_t)) pinos_log_warn ("loop %p: failed to write idle fd: %s", source, strerror (errno)); - } else { + } else if (!enabled && impl->enabled) { if (read (source->fd, &count, sizeof (uint64_t)) != sizeof (uint64_t)) pinos_log_warn ("loop %p: failed to read idle fd: %s", source, strerror (errno)); } + impl->enabled = enabled; } static void @@ -417,7 +427,7 @@ source_event_func (SpaSource *source) if (read (source->fd, &count, sizeof (uint64_t)) != sizeof (uint64_t)) pinos_log_warn ("loop %p: failed to read event fd: %s", source, strerror (errno)); - impl->func.event (source, source->data); + impl->func.event (&impl->impl->utils, source, source->data); } static SpaSource * @@ -437,6 +447,7 @@ loop_add_event (SpaLoopUtils *utils, source->source.data = data; source->source.fd = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK); source->source.mask = SPA_IO_IN; + source->impl = impl; source->close = true; source->func.event = func; @@ -465,7 +476,7 @@ source_timer_func (SpaSource *source) if (read (source->fd, &expires, sizeof (uint64_t)) != sizeof (uint64_t)) pinos_log_warn ("loop %p: failed to read timer fd: %s", source, strerror (errno)); - impl->func.timer (source, source->data); + impl->func.timer (&impl->impl->utils, source, source->data); } static SpaSource * @@ -485,6 +496,7 @@ loop_add_timer (SpaLoopUtils *utils, source->source.data = data; source->source.fd = timerfd_create (CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); source->source.mask = SPA_IO_IN; + source->impl = impl; source->close = true; source->func.timer = func; @@ -532,7 +544,7 @@ source_signal_func (SpaSource *source) if (read (source->fd, &signal_info, sizeof (signal_info)) != sizeof (signal_info)) pinos_log_warn ("loop %p: failed to read signal fd: %s", source, strerror (errno)); - impl->func.signal (source, impl->signal_number, source->data); + impl->func.signal (&impl->impl->utils, source, impl->signal_number, source->data); } static SpaSource * @@ -557,6 +569,7 @@ loop_add_signal (SpaLoopUtils *utils, source->source.fd = signalfd (-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); sigprocmask (SIG_BLOCK, &mask, NULL); source->source.mask = SPA_IO_IN; + source->impl = impl; source->close = true; source->func.signal = func; source->signal_number = signal_number; diff --git a/pinos/client/stream.c b/pinos/client/stream.c index ecc55e021..839922dfb 100644 --- a/pinos/client/stream.c +++ b/pinos/client/stream.c @@ -409,8 +409,9 @@ do_node_init (PinosStream *stream) } static void -on_timeout (SpaSource *source, - void *data) +on_timeout (SpaLoopUtils *utils, + SpaSource *source, + void *data) { PinosStream *stream = data; add_request_clock_update (stream); @@ -515,7 +516,8 @@ handle_rtnode_event (PinosStream *stream, } static void -on_rtsocket_condition (SpaSource *source, +on_rtsocket_condition (SpaLoopUtils *utils, + SpaSource *source, int fd, SpaIO mask, void *data) diff --git a/pinos/client/thread-mainloop.c b/pinos/client/thread-mainloop.c index ad1ed7214..653a0f5b8 100644 --- a/pinos/client/thread-mainloop.c +++ b/pinos/client/thread-mainloop.c @@ -57,8 +57,9 @@ post_hook (SpaLoopControl *ctrl, } static void -do_stop (SpaSource *source, - void *data) +do_stop (SpaLoopUtils *utils, + SpaSource *source, + void *data) { PinosThreadMainLoopImpl *impl = data; impl->running = false; diff --git a/pinos/daemon/pinos.conf.in b/pinos/daemon/pinos.conf.in index 4fe5d2866..0a5620229 100644 --- a/pinos/daemon/pinos.conf.in +++ b/pinos/daemon/pinos.conf.in @@ -3,4 +3,4 @@ load-module libpinos-module-protocol-native load-module libpinos-module-suspend-on-idle load-module libpinos-module-spa --pattern snow load-module libpinos-module-autolink -load-module libpinos-module-access +load-module libpinos-module-flatpak diff --git a/pinos/modules/meson.build b/pinos/modules/meson.build index 81d01ad00..7df92dd29 100644 --- a/pinos/modules/meson.build +++ b/pinos/modules/meson.build @@ -6,13 +6,13 @@ pinos_module_c_args = [ '-D_GNU_SOURCE', ] -pinos_module_access = shared_library('pinos-module-access', [ 'module-access.c' ], +pinos_module_flatpak = shared_library('pinos-module-flatpak', [ 'module-flatpak.c' ], c_args : pinos_module_c_args, include_directories : [configinc, spa_inc], link_with : spalib, install : true, install_dir : '@0@/pinos-0.1'.format(get_option('libdir')), - dependencies : [mathlib, dl_lib, pinos_dep, pinoscore_dep], + dependencies : [dbus_dep, mathlib, dl_lib, pinos_dep, pinoscore_dep], ) pinos_module_autolink = shared_library('pinos-module-autolink', [ 'module-autolink.c' ], diff --git a/pinos/modules/module-access.c b/pinos/modules/module-access.c deleted file mode 100644 index dd26ff167..000000000 --- a/pinos/modules/module-access.c +++ /dev/null @@ -1,188 +0,0 @@ -/* Pinos - * Copyright (C) 2016 Wim Taymans - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include -#include -#include - -#include "config.h" - -#include "pinos/server/core.h" -#include "pinos/server/module.h" - -typedef struct { - PinosCore *core; - PinosProperties *properties; - - PinosListener check_send; - PinosListener check_dispatch; -} ModuleImpl; - -static bool -check_global_owner (PinosCore *core, - PinosClient *client, - PinosGlobal *global) -{ - pinos_log_debug ("%p", global); - - if (global == NULL) - return false; - - pinos_log_debug ("%p", global->owner); - - if (global->owner == NULL) - return true; - - pinos_log_debug ("%d %d", global->owner->ucred.uid, client->ucred.uid); - - if (global->owner->ucred.uid == client->ucred.uid) - return true; - - return false; -} - -static SpaResult -do_view_global (PinosAccess *access, - PinosClient *client, - PinosGlobal *global) -{ - if (global->type == client->core->type.link) { - PinosLink *link = global->object; - - pinos_log_debug ("link %p: global %p %p %p %p", link, global->owner, client, link->output, link->input); - - /* we must be able to see both nodes */ - if (link->output && !check_global_owner (client->core, client, link->output->node->global)) - return SPA_RESULT_ERROR; - - pinos_log_debug ("link %p: global %p %p %p %p", link, global->owner, client, link->output, link->input); - - if (link->input && !check_global_owner (client->core, client, link->input->node->global)) - return SPA_RESULT_ERROR; - - pinos_log_debug ("link %p: global %p %p %p %p", link, global->owner, client, link->output, link->input); - } - else if (!check_global_owner (client->core, client, global)) - return SPA_RESULT_ERROR; - - return SPA_RESULT_OK; -} - -static SpaResult -do_create_node (PinosAccess *access, - PinosAccessData *data, - const char *factory_name, - const char *name, - PinosProperties *properties) -{ - data->res = SPA_RESULT_OK; - data->complete_cb (data); - return SPA_RESULT_OK; -} - -#if 0 -static void -check_timeout (SpaSource *source, - void *d) -{ - PinosAccessData *data = d; - - data->res = SPA_RESULT_OK; - data->complete_cb (data); -} -#endif - -static SpaResult -do_create_client_node (PinosAccess *access, - PinosAccessData *data, - const char *name, - PinosProperties *properties) -{ -#if 0 - struct timespec value; - SpaSource *timer; - - pinos_log_debug ("access %p: check %s %p", access, name, properties); - - timer = pinos_loop_add_timer (data->resource->core->main_loop->loop, - check_timeout, - data->async_copy (data, 0)); - value.tv_sec = 3; - value.tv_nsec = 0; - pinos_loop_update_timer (data->resource->core->main_loop->loop, - timer, - &value, - NULL, - false); - - return SPA_RESULT_RETURN_ASYNC (0); -#else - data->res = SPA_RESULT_OK; - return SPA_RESULT_OK; -#endif -} - -static PinosAccess access_checks = -{ - do_view_global, - do_create_node, - do_create_client_node, -}; - -static ModuleImpl * -module_new (PinosCore *core, - PinosProperties *properties) -{ - ModuleImpl *impl; - - impl = calloc (1, sizeof (ModuleImpl)); - pinos_log_debug ("module %p: new", impl); - - impl->core = core; - impl->properties = properties; - - core->access = &access_checks; - - return impl; -} - -#if 0 -static void -module_destroy (ModuleImpl *impl) -{ - pinos_log_debug ("module %p: destroy", impl); - - pinos_global_destroy (impl->global); - - pinos_signal_remove (&impl->global_added); - pinos_signal_remove (&impl->global_removed); - pinos_signal_remove (&impl->port_added); - pinos_signal_remove (&impl->port_removed); - pinos_signal_remove (&impl->port_unlinked); - pinos_signal_remove (&impl->link_state_changed); - free (impl); -} -#endif - -bool -pinos__module_init (PinosModule * module, const char * args) -{ - module_new (module->core, NULL); - return true; -} diff --git a/pinos/modules/module-flatpak.c b/pinos/modules/module-flatpak.c new file mode 100644 index 000000000..e596523c5 --- /dev/null +++ b/pinos/modules/module-flatpak.c @@ -0,0 +1,717 @@ +/* Pinos + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include + +#include "pinos/client/utils.h" +#include "pinos/server/core.h" +#include "pinos/server/module.h" + +typedef struct { + PinosCore *core; + PinosProperties *properties; + + DBusConnection *bus; + + PinosListener global_added; + PinosListener global_removed; + + SpaList client_list; + PinosAccess access; + + SpaSource *dispatch_event; +} ModuleImpl; + +typedef struct { + ModuleImpl *impl; + SpaList link; + PinosClient *client; + bool is_sandboxed; + SpaList async_pending; +} ClientInfo; + +typedef struct { + SpaList link; + bool handled; + ClientInfo *info; + char *handle; + PinosAccessData *access_data; +} AsyncPending; + +static ClientInfo * +find_client_info (ModuleImpl *impl, PinosClient *client) +{ + ClientInfo *info; + + spa_list_for_each (info, &impl->client_list, link) { + if (info->client == client) + return info; + } + return NULL; +} + +static void +close_request (AsyncPending *p) +{ + DBusMessage *m = NULL; + ModuleImpl *impl = p->info->impl; + + if (!(m = dbus_message_new_method_call ("org.freedesktop.portal.Request", + p->handle, + "org.freedesktop.portal.Request", + "Close"))) { + pinos_log_error ("Failed to create message"); + return; + } + + if (!dbus_connection_send (impl->bus, m, NULL)) + pinos_log_error ("Failed to send message"); + + dbus_message_unref(m); +} + +static AsyncPending * +find_pending (ClientInfo *cinfo, const char *handle) +{ + AsyncPending *p; + + spa_list_for_each (p, &cinfo->async_pending, link) { + if (strcmp (p->handle, handle) == 0) + return p; + } + return NULL; +} + +static void +free_pending (PinosAccessData *d) +{ + AsyncPending *p = d->user_data; + + if (!p->handled) + close_request(p); + + spa_list_remove (&p->link); + free (p->handle); +} + +static void +add_pending (ClientInfo *cinfo, const char *handle, PinosAccessData *access_data) +{ + AsyncPending *p; + PinosAccessData *ad; + + ad = access_data->async_copy (access_data, sizeof (AsyncPending)); + ad->free_cb = free_pending; + + p = ad->user_data; + p->info = cinfo; + p->handle = strdup (handle); + p->access_data = ad; + p->handled = false; + + spa_list_insert (cinfo->async_pending.prev, &p->link); +} + +static void +client_info_free (ClientInfo *cinfo) +{ + AsyncPending *p, *tmp; + + spa_list_for_each_safe (p, tmp, &cinfo->async_pending, link) { + p->access_data->res = SPA_RESULT_NO_PERMISSION; + p->access_data->complete_cb (p->access_data); + } + spa_list_remove (&cinfo->link); + free (cinfo); +} + +static bool +client_is_sandboxed (PinosClient *cl) +{ + char data[2048], *ptr; + size_t n, size; + const char *state = NULL; + const char *current; + bool result; + int fd; + pid_t pid; + + if (cl->ucred_valid) { + pinos_log_info ("client has trusted pid %d", cl->ucred.pid); + } + else { + pinos_log_info ("no trusted pid found, assuming not sandboxed\n"); + return false; + } + + pid = cl->ucred.pid; + + sprintf (data, "/proc/%u/cgroup", pid); + fd = open (data, O_RDONLY | O_CLOEXEC, 0); + if (fd == -1) + return false; + + size = sizeof (data); + ptr = data; + + while (size > 0) { + int r; + + if ((r = read (fd, data, size)) < 0) { + if (errno == EINTR) + continue; + else + break; + } + if (r == 0) + break; + + ptr += r; + size -= r; + } + close (fd); + + result = false; + while ((current = pinos_split_walk (data, "\n", &n, &state)) != NULL) { + if (strncmp (current, "1:name=systemd:", strlen ("1:name=systemd:")) == 0) { + const char *p = strstr (current, "flatpak-"); + if (p && p - current < n) { + pinos_log_info ("found a flatpak cgroup, assuming sandboxed\n"); + result = true; + break; + } + } + } + return result; +} + +static bool +check_global_owner (PinosCore *core, + PinosClient *client, + PinosGlobal *global) +{ + if (global == NULL) + return false; + + if (global->owner == NULL) + return true; + + if (global->owner->ucred.uid == client->ucred.uid) + return true; + + return false; +} + +static SpaResult +do_view_global (PinosAccess *access, + PinosClient *client, + PinosGlobal *global) +{ + if (global->type == client->core->type.link) { + PinosLink *link = global->object; + + /* we must be able to see both nodes */ + if (link->output && !check_global_owner (client->core, client, link->output->node->global)) + return SPA_RESULT_ERROR; + + if (link->input && !check_global_owner (client->core, client, link->input->node->global)) + return SPA_RESULT_ERROR; + } + else if (!check_global_owner (client->core, client, global)) + return SPA_RESULT_ERROR; + + return SPA_RESULT_OK; +} + +static SpaResult +do_create_node (PinosAccess *access, + PinosAccessData *data, + const char *factory_name, + const char *name, + PinosProperties *properties) +{ + ModuleImpl *impl = SPA_CONTAINER_OF (access, ModuleImpl, access); + ClientInfo *cinfo = find_client_info (impl, data->resource->client); + + if (cinfo->is_sandboxed) + data->res = SPA_RESULT_NO_PERMISSION; + else + data->res = SPA_RESULT_OK; + + data->complete_cb (data); + return SPA_RESULT_OK; +} + + +static DBusHandlerResult +portal_response (DBusConnection *connection, DBusMessage *msg, void *user_data) +{ + ClientInfo *cinfo = user_data; + + if (dbus_message_is_signal (msg, "org.freedesktop.portal.Request", "Response")) { + uint32_t response = 2; + DBusError error; + AsyncPending *p; + PinosAccessData *d; + + dbus_error_init (&error); + + dbus_connection_remove_filter (connection, portal_response, cinfo); + + if (!dbus_message_get_args (msg, &error, DBUS_TYPE_UINT32, &response, DBUS_TYPE_INVALID)) { + pinos_log_error("failed to parse Response: %s", error.message); + dbus_error_free(&error); + } + + p = find_pending (cinfo, dbus_message_get_path (msg)); + if (p == NULL) + return DBUS_HANDLER_RESULT_HANDLED; + + p->handled = true; + d = p->access_data; + + pinos_log_debug ("portal check result: %d", response); + + d->res = response == 0 ? SPA_RESULT_OK : SPA_RESULT_NO_PERMISSION; + d->complete_cb (d); + + return DBUS_HANDLER_RESULT_HANDLED; + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static SpaResult +do_create_client_node (PinosAccess *access, + PinosAccessData *data, + const char *name, + PinosProperties *properties) +{ + ModuleImpl *impl = SPA_CONTAINER_OF (access, ModuleImpl, access); + ClientInfo *cinfo = find_client_info (impl, data->resource->client); + DBusMessage *m = NULL, *r = NULL; + DBusError error; + pid_t pid; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + const char *handle; + const char *device; + + if (!cinfo->is_sandboxed) { + data->res = SPA_RESULT_OK; + data->complete_cb (data); + return SPA_RESULT_OK; + } + + pinos_log_info ("ask portal for client %p", cinfo->client); + + dbus_error_init(&error); + + if (!(m = dbus_message_new_method_call ("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Device", + "AccessDevice"))) + goto no_method_call; + + device = "camera"; + + pid = cinfo->client->ucred.pid; + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &pid, + DBUS_TYPE_INVALID)) + goto message_failed; + + dbus_message_iter_init_append(m, &msg_iter); + dbus_message_iter_open_container (&msg_iter, DBUS_TYPE_ARRAY, "s", &dict_iter); + dbus_message_iter_append_basic (&dict_iter, DBUS_TYPE_STRING, &device); + dbus_message_iter_close_container (&msg_iter, &dict_iter); + + dbus_message_iter_open_container (&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + dbus_message_iter_close_container (&msg_iter, &dict_iter); + + if (!(r = dbus_connection_send_with_reply_and_block (impl->bus, m, -1, &error))) + goto send_failed; + + dbus_message_unref(m); + + if (!dbus_message_get_args (r, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID)) + goto parse_failed; + + dbus_message_unref (r); + + dbus_bus_add_match (impl->bus, + "type='signal',interface='org.freedesktop.portal.Request'", + &error); + dbus_connection_flush (impl->bus); + if (dbus_error_is_set (&error)) + goto subscribe_failed; + + dbus_connection_add_filter (impl->bus, portal_response, cinfo, NULL); + + add_pending (cinfo, handle, data); + + return SPA_RESULT_RETURN_ASYNC (0); + +no_method_call: + pinos_log_error ("Failed to create message"); + return SPA_RESULT_NO_PERMISSION; +message_failed: + dbus_message_unref(m); + return SPA_RESULT_NO_PERMISSION; +send_failed: + pinos_log_error ("Failed to call portal: %s", error.message); + dbus_error_free(&error); + dbus_message_unref(m); + return SPA_RESULT_NO_PERMISSION; +parse_failed: + pinos_log_error ("Failed to parse AccessDevice result: %s", error.message); + dbus_error_free (&error); + dbus_message_unref (r); + return SPA_RESULT_NO_PERMISSION; +subscribe_failed: + pinos_log_error ("Failed to subscribe to Request signal: %s", error.message); + dbus_error_free(&error); + return SPA_RESULT_NO_PERMISSION; +} + +static PinosAccess access_checks = +{ + do_view_global, + do_create_node, + do_create_client_node, +}; + +static void +on_global_added (PinosListener *listener, + PinosCore *core, + PinosGlobal *global) +{ + ModuleImpl *impl = SPA_CONTAINER_OF (listener, ModuleImpl, global_added); + + if (global->type == impl->core->type.client) { + PinosClient *client = global->object; + ClientInfo *cinfo; + + cinfo = calloc (1, sizeof (ClientInfo)); + cinfo->impl = impl; + cinfo->client = client; + cinfo->is_sandboxed = client_is_sandboxed (client); + cinfo->is_sandboxed = true; + spa_list_init (&cinfo->async_pending); + + spa_list_insert (impl->client_list.prev, &cinfo->link); + + pinos_log_debug ("module %p: client %p added", impl, client); + } +} + +static void +on_global_removed (PinosListener *listener, + PinosCore *core, + PinosGlobal *global) +{ + ModuleImpl *impl = SPA_CONTAINER_OF (listener, ModuleImpl, global_removed); + + if (global->type == impl->core->type.client) { + PinosClient *client = global->object; + ClientInfo *cinfo; + + if ((cinfo = find_client_info (impl, client))) + client_info_free (cinfo); + + pinos_log_debug ("module %p: client %p removed", impl, client); + } +} + +static void +dispatch_cb (SpaLoopUtils *utils, SpaSource *source, void *userdata) +{ + ModuleImpl *impl = userdata; + + if (dbus_connection_dispatch (impl->bus) == DBUS_DISPATCH_COMPLETE) + pinos_loop_enable_idle (impl->core->main_loop->loop, source, false); +} + +static void +dispatch_status (DBusConnection *conn, DBusDispatchStatus status, void *userdata) +{ + ModuleImpl *impl = userdata; + + pinos_loop_enable_idle (impl->core->main_loop->loop, + impl->dispatch_event, + status == DBUS_DISPATCH_COMPLETE ? false : true); +} + +static inline SpaIO +dbus_to_io (DBusWatch *watch) +{ + SpaIO mask; + unsigned int flags; + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled (watch)) + return 0; + + flags = dbus_watch_get_flags (watch); + mask = SPA_IO_HUP | SPA_IO_ERR; + + if (flags & DBUS_WATCH_READABLE) + mask |= SPA_IO_IN; + if (flags & DBUS_WATCH_WRITABLE) + mask |= SPA_IO_OUT; + + return mask; +} + +static inline unsigned int +io_to_dbus (SpaIO mask) +{ + unsigned int flags = 0; + + if (mask & SPA_IO_IN) + flags |= DBUS_WATCH_READABLE; + if (mask & SPA_IO_OUT) + flags |= DBUS_WATCH_WRITABLE; + if (mask & SPA_IO_HUP) + flags |= DBUS_WATCH_HANGUP; + if (mask & SPA_IO_ERR) + flags |= DBUS_WATCH_ERROR; + return flags; +} + +static void +handle_io_event (SpaLoopUtils *utils, + SpaSource *source, + int fd, + SpaIO mask, + void *userdata) +{ + DBusWatch *watch = userdata; + + if (!dbus_watch_get_enabled (watch)) { + pinos_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd); + return; + } + dbus_watch_handle (watch, io_to_dbus (mask)); +} + +static dbus_bool_t +add_watch (DBusWatch *watch, void *userdata) +{ + ModuleImpl *impl = userdata; + SpaSource *source; + + pinos_log_debug ("add watch %p %d", watch, dbus_watch_get_unix_fd (watch)); + + /* we dup because dbus tends to add the same fd multiple times and our epoll + * implementation does not like that */ + source = pinos_loop_add_io (impl->core->main_loop->loop, + dup (dbus_watch_get_unix_fd (watch)), + dbus_to_io (watch), + true, + handle_io_event, + watch); + + dbus_watch_set_data (watch, source, NULL); + return TRUE; +} + +static void +remove_watch (DBusWatch *watch, void *userdata) +{ + ModuleImpl *impl = userdata; + SpaSource *source; + + if ((source = dbus_watch_get_data (watch))) + pinos_loop_destroy_source (impl->core->main_loop->loop, source); +} + +static void +toggle_watch (DBusWatch *watch, void *userdata) +{ + ModuleImpl *impl = userdata; + SpaSource *source; + + source = dbus_watch_get_data (watch); + + pinos_loop_update_io (impl->core->main_loop->loop, + source, + dbus_to_io (watch)); +} + +static void +handle_timer_event (SpaLoopUtils *utils, SpaSource *source, void *userdata) +{ + DBusTimeout *timeout = userdata; + uint64_t t; + struct timespec ts; + + if (dbus_timeout_get_enabled (timeout)) { + t = dbus_timeout_get_interval (timeout) * SPA_NSEC_PER_MSEC; + ts.tv_sec = t / SPA_NSEC_PER_SEC; + ts.tv_nsec = t % SPA_NSEC_PER_SEC; + spa_loop_utils_update_timer (utils, source, &ts, NULL, false); + + dbus_timeout_handle (timeout); + } +} + +static dbus_bool_t +add_timeout (DBusTimeout *timeout, void *userdata) +{ + ModuleImpl *impl = userdata; + SpaSource *source; + struct timespec ts; + uint64_t t; + + if (!dbus_timeout_get_enabled (timeout)) + return FALSE; + + source = pinos_loop_add_timer (impl->core->main_loop->loop, + handle_timer_event, + timeout); + + dbus_timeout_set_data (timeout, source, NULL); + + t = dbus_timeout_get_interval (timeout) * SPA_NSEC_PER_MSEC; + ts.tv_sec = t / SPA_NSEC_PER_SEC; + ts.tv_nsec = t % SPA_NSEC_PER_SEC; + pinos_loop_update_timer (impl->core->main_loop->loop, + source, + &ts, + NULL, + false); + return TRUE; +} + +static void +remove_timeout (DBusTimeout *timeout, void *userdata) +{ + ModuleImpl *impl = userdata; + SpaSource *source; + + if ((source = dbus_timeout_get_data (timeout))) + pinos_loop_destroy_source (impl->core->main_loop->loop, source); +} + +static void +toggle_timeout (DBusTimeout *timeout, void *userdata) +{ + ModuleImpl *impl = userdata; + SpaSource *source; + struct timespec ts, *tsp; + + source = dbus_timeout_get_data (timeout); + + if (dbus_timeout_get_enabled (timeout)) { + uint64_t t = dbus_timeout_get_interval (timeout) * SPA_NSEC_PER_MSEC; + ts.tv_sec = t / SPA_NSEC_PER_SEC; + ts.tv_nsec = t % SPA_NSEC_PER_SEC; + tsp = &ts; + } + else { + tsp = NULL; + } + pinos_loop_update_timer (impl->core->main_loop->loop, source, tsp, NULL, false); +} + +static void +wakeup_main (void *userdata) +{ + ModuleImpl *impl = userdata; + + pinos_loop_enable_idle (impl->core->main_loop->loop, + impl->dispatch_event, + true); +} + + +static ModuleImpl * +module_new (PinosCore *core, + PinosProperties *properties) +{ + ModuleImpl *impl; + DBusError error; + + dbus_error_init(&error); + + impl = calloc (1, sizeof (ModuleImpl)); + pinos_log_debug ("module %p: new", impl); + + impl->core = core; + impl->properties = properties; + impl->access = access_checks; + + impl->bus = dbus_bus_get_private (DBUS_BUS_SESSION, &error); + if (impl->bus == NULL) + goto error; + + impl->dispatch_event = pinos_loop_add_idle (core->main_loop->loop, + false, + dispatch_cb, + impl); + + dbus_connection_set_exit_on_disconnect (impl->bus, false); + dbus_connection_set_dispatch_status_function (impl->bus, dispatch_status, impl, NULL); + dbus_connection_set_watch_functions (impl->bus, add_watch, remove_watch, toggle_watch, impl, NULL); + dbus_connection_set_timeout_functions (impl->bus, add_timeout, remove_timeout, toggle_timeout, impl, NULL); + dbus_connection_set_wakeup_main_function (impl->bus, wakeup_main, impl, NULL); + + core->access = &impl->access; + + spa_list_init (&impl->client_list); + + pinos_signal_add (&core->global_added, &impl->global_added, on_global_added); + pinos_signal_add (&core->global_removed, &impl->global_removed, on_global_removed); + + return impl; + +error: + pinos_log_error ("Failed to connect to system bus: %s", error.message); + dbus_error_free (&error); + return NULL; +} + +#if 0 +static void +module_destroy (ModuleImpl *impl) +{ + pinos_log_debug ("module %p: destroy", impl); + + dbus_connection_close (impl->bus); + dbus_connection_unref (impl->bus); + free (impl); +} +#endif + +bool +pinos__module_init (PinosModule * module, const char * args) +{ + module_new (module->core, NULL); + return true; +} diff --git a/pinos/modules/module-protocol-native.c b/pinos/modules/module-protocol-native.c index 76446d72b..0fcba30c9 100644 --- a/pinos/modules/module-protocol-native.c +++ b/pinos/modules/module-protocol-native.c @@ -120,10 +120,11 @@ on_before_iterate (PinosListener *listener, } static void -connection_data (SpaSource *source, - int fd, - SpaIO mask, - void *data) +connection_data (SpaLoopUtils *utils, + SpaSource *source, + int fd, + SpaIO mask, + void *data) { PinosProtocolNativeClient *client = data; PinosConnection *conn = client->connection; @@ -330,10 +331,11 @@ err: } static void -socket_data (SpaSource *source, - int fd, - SpaIO mask, - void *data) +socket_data (SpaLoopUtils *utils, + SpaSource *source, + int fd, + SpaIO mask, + void *data) { PinosProtocolNative *impl = data; PinosProtocolNativeClient *client; diff --git a/pinos/modules/module-suspend-on-idle.c b/pinos/modules/module-suspend-on-idle.c index 620eefd75..4d4bbef5b 100644 --- a/pinos/modules/module-suspend-on-idle.c +++ b/pinos/modules/module-suspend-on-idle.c @@ -77,8 +77,9 @@ node_info_free (NodeInfo *info) } static void -idle_timeout (SpaSource *source, - void *data) +idle_timeout (SpaLoopUtils *utils, + SpaSource *source, + void *data) { NodeInfo *info = data; diff --git a/pinos/server/access.h b/pinos/server/access.h index f2757ef4b..d9586ee01 100644 --- a/pinos/server/access.h +++ b/pinos/server/access.h @@ -38,10 +38,10 @@ typedef struct _PinosAccessData PinosAccessData; struct _PinosAccessData { SpaResult res; PinosResource *resource; - void *(*async_copy) (PinosAccessData *data, size_t size); + void * (*async_copy) (PinosAccessData *data, size_t size); void (*complete_cb) (PinosAccessData *data); void (*free_cb) (PinosAccessData *data); - void *user_data; + void * user_data; }; diff --git a/pinos/server/client.c b/pinos/server/client.c index 048842207..cad2a2544 100644 --- a/pinos/server/client.c +++ b/pinos/server/client.c @@ -102,6 +102,7 @@ pinos_client_new (PinosCore *core, this->properties = properties; spa_list_init (&this->resource_list); + pinos_signal_init (&this->properties_changed); pinos_signal_init (&this->resource_added); pinos_signal_init (&this->resource_removed); @@ -185,6 +186,8 @@ pinos_client_update_properties (PinosClient *client, client->info.change_mask = 1 << 0; client->info.props = client->properties ? &client->properties->dict : NULL; + pinos_signal_emit (&client->properties_changed, client); + spa_list_for_each (resource, &client->resource_list, link) { pinos_client_notify_info (resource, &client->info); } diff --git a/pinos/server/client.h b/pinos/server/client.h index 524e867c5..e8eea176e 100644 --- a/pinos/server/client.h +++ b/pinos/server/client.h @@ -46,6 +46,9 @@ struct _PinosClient { PinosGlobal *global; PinosProperties *properties; + PINOS_SIGNAL (properties_changed, (PinosListener *listener, + PinosClient *client)); + PinosClientInfo info; bool ucred_valid; struct ucred ucred; diff --git a/pinos/server/data-loop.c b/pinos/server/data-loop.c index 04940a03c..759a43f0b 100644 --- a/pinos/server/data-loop.c +++ b/pinos/server/data-loop.c @@ -111,8 +111,9 @@ do_loop (void *user_data) static void -do_stop (SpaSource *source, - void *data) +do_stop (SpaLoopUtils *utils, + SpaSource *source, + void *data) { PinosDataLoopImpl *impl = data; impl->running = false; diff --git a/pinos/server/work-queue.c b/pinos/server/work-queue.c index cd3bb50c9..53d3c687f 100644 --- a/pinos/server/work-queue.c +++ b/pinos/server/work-queue.c @@ -48,8 +48,9 @@ typedef struct static void -process_work_queue (SpaSource *source, - void *data) +process_work_queue (SpaLoopUtils *utils, + SpaSource *source, + void *data) { PinosWorkQueueImpl *impl = data; PinosWorkQueue *this = &impl->this; diff --git a/spa/include/spa/defs.h b/spa/include/spa/defs.h index a4f436877..0c9c6cf16 100644 --- a/spa/include/spa/defs.h +++ b/spa/include/spa/defs.h @@ -123,9 +123,12 @@ typedef void (*SpaNotify) (void *data); #define SPA_IDX_INVALID ((unsigned int)-1) #define SPA_ID_INVALID ((uint32_t)0xffffffff) -#define SPA_NSEC_PER_SEC (1000000000ll) -#define SPA_USEC_PER_SEC (1000000ll) -#define SPA_MSEC_PER_SEC (1000ll) +#define SPA_NSEC_PER_SEC (1000000000ll) +#define SPA_NSEC_PER_MSEC (1000000ll) +#define SPA_NSEC_PER_USEC (1000ll) +#define SPA_USEC_PER_SEC (1000000ll) +#define SPA_USEC_PER_MSEC (1000ll) +#define SPA_MSEC_PER_SEC (1000ll) #define SPA_TIMESPEC_TO_TIME(ts) ((ts)->tv_sec * SPA_NSEC_PER_SEC + (ts)->tv_nsec) #define SPA_TIMEVAL_TO_TIME(tv) ((tv)->tv_sec * SPA_NSEC_PER_SEC + (tv)->tv_usec * 1000ll) diff --git a/spa/include/spa/loop.h b/spa/include/spa/loop.h index 4502c2f2f..f4297fbee 100644 --- a/spa/include/spa/loop.h +++ b/spa/include/spa/loop.h @@ -126,19 +126,24 @@ struct _SpaLoopControl { #define spa_loop_control_leave(l) (l)->leave(l) -typedef void (*SpaSourceIOFunc) (SpaSource *source, - int fd, - SpaIO mask, - void *data); -typedef void (*SpaSourceIdleFunc) (SpaSource *source, - void *data); -typedef void (*SpaSourceEventFunc) (SpaSource *source, - void *data); -typedef void (*SpaSourceTimerFunc) (SpaSource *source, - void *data); -typedef void (*SpaSourceSignalFunc) (SpaSource *source, - int signal_number, - void *data); +typedef void (*SpaSourceIOFunc) (SpaLoopUtils *utils, + SpaSource *source, + int fd, + SpaIO mask, + void *data); +typedef void (*SpaSourceIdleFunc) (SpaLoopUtils *utils, + SpaSource *source, + void *data); +typedef void (*SpaSourceEventFunc) (SpaLoopUtils *utils, + SpaSource *source, + void *data); +typedef void (*SpaSourceTimerFunc) (SpaLoopUtils *utils, + SpaSource *source, + void *data); +typedef void (*SpaSourceSignalFunc) (SpaLoopUtils *utils, + SpaSource *source, + int signal_number, + void *data); /** * SpaLoopUtils: @@ -160,6 +165,7 @@ struct _SpaLoopUtils { SpaIO mask); SpaSource * (*add_idle) (SpaLoopUtils *utils, + bool enabled, SpaSourceIdleFunc func, void *data); void (*enable_idle) (SpaSource *source,