diff options
author | Carlos Garnacho <carlosg@gnome.org> | 2020-01-22 18:39:26 +0100 |
---|---|---|
committer | Carlos Garnacho <carlosg@gnome.org> | 2020-07-17 09:33:38 +0200 |
commit | 41017d120d480957d7a44ba662f290a6415e840c (patch) | |
tree | 925b8b233ef194dcf33dba5af1fc2f0762bd88c2 /src | |
parent | ac3eeb8b730eb6959240c4c236385152bb04143c (diff) | |
download | tracker-41017d120d480957d7a44ba662f290a6415e840c.tar.gz |
portal: Add XDG portal implementation
Diffstat (limited to 'src')
-rw-r--r-- | src/meson.build | 3 | ||||
-rw-r--r-- | src/portal/meson.build | 14 | ||||
-rw-r--r-- | src/portal/tracker-main.c | 153 | ||||
-rw-r--r-- | src/portal/tracker-portal-utils.c | 176 | ||||
-rw-r--r-- | src/portal/tracker-portal-utils.h | 31 | ||||
-rw-r--r-- | src/portal/tracker-portal.c | 298 | ||||
-rw-r--r-- | src/portal/tracker-portal.h | 33 |
7 files changed, 708 insertions, 0 deletions
diff --git a/src/meson.build b/src/meson.build index 2f9817ece..78f21b9b9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,3 +16,6 @@ subdir('libtracker-sparql') # Public commandline control tool subdir('tracker') + +# XDG Portal +subdir('portal') diff --git a/src/portal/meson.build b/src/portal/meson.build new file mode 100644 index 000000000..9b801d975 --- /dev/null +++ b/src/portal/meson.build @@ -0,0 +1,14 @@ +sources = [ + 'tracker-main.c', + 'tracker-portal.c', + 'tracker-portal-utils.c', +] + +executable('tracker-xdg-portal-@0@'.format(tracker_api_major), sources, + c_args: tracker_c_args, + install: true, + install_rpath: tracker_install_rpath, + install_dir: get_option('libexecdir'), + dependencies: [tracker_sparql_dep], + include_directories: [commoninc, configinc, srcinc], +) diff --git a/src/portal/tracker-main.c b/src/portal/tracker-main.c new file mode 100644 index 000000000..e8977fc4b --- /dev/null +++ b/src/portal/tracker-main.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2020, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + */ + +#include "config.h" + +#include <stdlib.h> +#include <locale.h> + +#include <glib.h> +#include <glib-unix.h> +#include <glib/gi18n.h> +#include <gio/gio.h> + +#include <libtracker-common/tracker-common.h> + +#include "tracker-portal.h" + +static gboolean version = FALSE; + +const char usage_string[] = + "xdg-tracker-portal [--version | -v]"; + +const char about[] = + "Tracker XDG portal " PACKAGE_VERSION "\n" + "\n" + "This program is free software and comes without any warranty.\n" + "It is licensed under version 2 or later of the General Public " + "License which can be viewed at:\n" + "\n" + " http://www.gnu.org/licenses/gpl.txt" + "\n"; + +static GOptionEntry entries[] = { + { "version", 'v', 0, G_OPTION_ARG_NONE, &version, + N_("Version"), + NULL + }, +}; + +static int +print_version (void) +{ + puts (about); + return 0; +} + +static gboolean +sigterm_cb (gpointer user_data) +{ + g_main_loop_quit (user_data); + + return G_SOURCE_REMOVE; +} + +static void +name_acquired_callback (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_debug ("Name '%s' acquired", name); + g_main_loop_quit (user_data); +} + +static void +name_lost_callback (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_critical ("Name '%s' lost", name); + exit (EXIT_FAILURE); +} + +int +main (int argc, char *argv[]) +{ + GMainLoop *main_loop; + g_autoptr(GError) error = NULL; + GDBusConnection *connection; + g_autoptr(GOptionContext) context = NULL; + TrackerPortal *portal; + + setlocale (LC_ALL, ""); + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + + if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) { + g_printerr ("%s, %s\n", _("Unrecognized options"), error->message); + g_error_free (error); + return EXIT_FAILURE; + } + + if (version) { + print_version (); + return EXIT_SUCCESS; + } + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (!connection) { + g_printerr ("%s", error->message); + return EXIT_FAILURE; + } + + main_loop = g_main_loop_new (NULL, FALSE); + + g_bus_own_name_on_connection (connection, + "org.freedesktop.portal.Tracker", + G_BUS_NAME_OWNER_FLAGS_NONE, + name_acquired_callback, + name_lost_callback, + g_main_loop_ref (main_loop), + (GDestroyNotify) g_main_loop_unref); + + g_main_loop_run (main_loop); + + portal = tracker_portal_new (connection, NULL, &error); + if (!portal) { + g_printerr ("%s", error->message); + return EXIT_FAILURE; + } + + g_unix_signal_add (SIGINT, sigterm_cb, main_loop); + g_unix_signal_add (SIGTERM, sigterm_cb, main_loop); + g_main_loop_run (main_loop); + + g_main_loop_unref (main_loop); + + g_clear_object (&portal); + + return EXIT_FAILURE; +} diff --git a/src/portal/tracker-portal-utils.c b/src/portal/tracker-portal-utils.c new file mode 100644 index 000000000..2582a720e --- /dev/null +++ b/src/portal/tracker-portal-utils.c @@ -0,0 +1,176 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Alexander Larsson <alexl@redhat.com> + * + * Code borrowed from xdg-desktop-portal/src/xdp-utils.[ch] + */ + +#include "config.h" + +#include <glib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include "tracker-portal-utils.h" + +#define DBUS_NAME_DBUS "org.freedesktop.DBus" +#define DBUS_INTERFACE_DBUS DBUS_NAME_DBUS +#define DBUS_PATH_DBUS "/org/freedesktop/DBus" + +static GKeyFile * +parse_app_info_from_flatpak_info (int pid, GError **error) +{ + g_autofree char *root_path = NULL; + int root_fd = -1; + int info_fd = -1; + struct stat stat_buf; + g_autoptr(GError) local_error = NULL; + g_autoptr(GMappedFile) mapped = NULL; + g_autoptr(GKeyFile) metadata = NULL; + g_autofree char *id = NULL; + + root_path = g_strdup_printf ("/proc/%u/root", pid); + root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + if (root_fd == -1) { + /* Not able to open the root dir shouldn't happen. Probably the app died and + * we're failing due to /proc/$pid not existing. In that case fail instead + * of treating this as privileged. */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unable to open %s", root_path); + return NULL; + } + + metadata = g_key_file_new (); + + info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY); + close (root_fd); + if (info_fd == -1) { + if (errno == ENOENT) { + /* No file => on the host, return NULL with no error */ + return NULL; + } + + /* Some weird error => failure */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unable to open application info file"); + return NULL; + } + + if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) { + /* Some weird fd => failure */ + close (info_fd); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unable to open application info file"); + return NULL; + } + + mapped = g_mapped_file_new_from_fd (info_fd, FALSE, &local_error); + if (mapped == NULL) { + close (info_fd); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't map .flatpak-info file: %s", local_error->message); + return NULL; + } + + if (!g_key_file_load_from_data (metadata, + g_mapped_file_get_contents (mapped), + g_mapped_file_get_length (mapped), + G_KEY_FILE_NONE, &local_error)) { + close (info_fd); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't load .flatpak-info file: %s", local_error->message); + return NULL; + } + + close (info_fd); + + return g_steal_pointer (&metadata); +} + +static GKeyFile * +tracker_connection_lookup_app_info_sync (GDBusConnection *connection, + const char *sender, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GDBusMessage) msg = NULL; + g_autoptr(GDBusMessage) reply = NULL; + GVariant *body; + g_autoptr(GVariantIter) iter = NULL; + g_autoptr(GKeyFile) app_info = NULL; + const char *key; + GVariant *value; + g_autoptr(GError) local_error = NULL; + guint32 pid = 0; + + msg = g_dbus_message_new_method_call (DBUS_NAME_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionCredentials"); + g_dbus_message_set_body (msg, g_variant_new ("(s)", sender)); + + reply = g_dbus_connection_send_message_with_reply_sync (connection, msg, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + 30000, + NULL, + cancellable, + error); + if (reply == NULL) + return NULL; + + if (g_dbus_message_get_message_type (reply) == G_DBUS_MESSAGE_TYPE_ERROR) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't find peer app id"); + return NULL; + } + + body = g_dbus_message_get_body (reply); + + g_variant_get (body, "(a{sv})", &iter); + while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { + if (strcmp (key, "ProcessID") == 0) + pid = g_variant_get_uint32 (value); + } + + if (pid == 0) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't find app PID"); + return NULL; + } + + app_info = parse_app_info_from_flatpak_info (pid, &local_error); + + if (app_info == NULL && local_error) { + g_propagate_error (error, g_steal_pointer (&local_error)); + return NULL; + } + + return g_steal_pointer (&app_info); +} + +GKeyFile * +tracker_invocation_lookup_app_info_sync (GDBusMethodInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation); + const gchar *sender = g_dbus_method_invocation_get_sender (invocation); + + return tracker_connection_lookup_app_info_sync (connection, sender, cancellable, error); +} diff --git a/src/portal/tracker-portal-utils.h b/src/portal/tracker-portal-utils.h new file mode 100644 index 000000000..dcae4a0e3 --- /dev/null +++ b/src/portal/tracker-portal-utils.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Alexander Larsson <alexl@redhat.com> + * + * Code borrowed from xdg-desktop-portal/src/xdp-utils.[ch] + */ +#ifndef __TRACKER_PORTAL_UTILS_H__ +#define __TRACKER_PORTAL_UTILS_H__ + +#include <gio/gio.h> + +GKeyFile * tracker_invocation_lookup_app_info_sync (GDBusMethodInvocation *invocation, + GCancellable *cancellable, + GError **error); + +#endif /* __TRACKER_PORTAL_UTILS_H__ */ diff --git a/src/portal/tracker-portal.c b/src/portal/tracker-portal.c new file mode 100644 index 000000000..87c2832e3 --- /dev/null +++ b/src/portal/tracker-portal.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2020, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + */ + +#include "config.h" + +#include <libtracker-sparql/tracker-sparql.h> +#include <libtracker-common/tracker-common.h> + +#include "tracker-portal.h" + +typedef struct _TrackerPortal TrackerPortal; +typedef struct _TrackerSession TrackerSession; + +struct _TrackerSession +{ + TrackerSparqlConnection *connection; + GDBusConnection *dbus_connection; + TrackerEndpoint *endpoint; + gchar *object_path; +}; + +struct _TrackerPortal +{ + GObject parent_instance; + GDBusConnection *dbus_connection; + guint register_id; + GDBusNodeInfo *node_info; + GCancellable *cancellable; + GArray *sessions; + guint64 session_ids; +}; + +enum +{ + PROP_DBUS_CONNECTION = 1, + N_PROPS +}; + +static GParamSpec *props[N_PROPS] = { 0 }; + +static void tracker_portal_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (TrackerPortal, tracker_portal, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, tracker_portal_initable_iface_init)) + +static const gchar portal_xml[] = + "<node>" + " <interface name='org.freedesktop.portal.Tracker'>" + " <method name='CreateSession'>" + " <arg type='s' name='service' direction='in' />" + " <arg type='o' name='result' direction='out' />" + " </method>" + " </interface>" + "</node>"; + +static void +tracker_portal_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TrackerPortal *portal = TRACKER_PORTAL (object); + + switch (prop_id) { + case PROP_DBUS_CONNECTION: + portal->dbus_connection = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +tracker_portal_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TrackerPortal *portal = TRACKER_PORTAL (object); + + switch (prop_id) { + case PROP_DBUS_CONNECTION: + g_value_set_object (value, portal->dbus_connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +tracker_portal_finalize (GObject *object) +{ + TrackerPortal *portal = TRACKER_PORTAL (object); + + g_debug ("Finalizing Tracker portal"); + + g_clear_pointer (&portal->sessions, g_array_unref); + + if (portal->register_id != 0) { + g_dbus_connection_unregister_object (portal->dbus_connection, + portal->register_id); + portal->register_id = 0; + } + + g_clear_object (&portal->dbus_connection); + g_clear_pointer (&portal->node_info, + g_dbus_node_info_unref); + + G_OBJECT_CLASS (tracker_portal_parent_class)->finalize (object); + g_debug ("Tracker portal finalized"); +} + +static void +tracker_portal_class_init (TrackerPortalClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = tracker_portal_set_property; + object_class->get_property = tracker_portal_get_property; + object_class->finalize = tracker_portal_finalize; + + props[PROP_DBUS_CONNECTION] = + g_param_spec_object ("dbus-connection", + "DBus connection", + "DBus connection", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, props); +} + +static void +clear_session (gpointer user_data) +{ + TrackerSession *session = user_data; + + g_debug ("Closing session '%s'", session->object_path); + g_clear_object (&session->endpoint); + g_clear_object (&session->connection); + g_clear_object (&session->dbus_connection); + g_clear_pointer (&session->object_path, g_free); +} + +static void +tracker_portal_init (TrackerPortal *portal) +{ + portal->sessions = g_array_new (FALSE, TRUE, sizeof (TrackerSession)); + g_array_set_clear_func (portal->sessions, clear_session); +} + +static void +portal_iface_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + TrackerPortal *portal = user_data; + + if (g_strcmp0 (method_name, "CreateSession") == 0) { + g_autofree gchar *uri = NULL; + g_autofree gchar *service = NULL; + g_autofree gchar *object_path = NULL; + g_autofree gchar *session_object_path = NULL; + g_autoptr(GDBusConnection) dbus_connection = NULL; + g_autoptr(TrackerSparqlConnection) connection = NULL; + g_autoptr(TrackerEndpoint) endpoint = NULL; + g_autoptr(GError) error = NULL; + GBusType bus_type; + TrackerSession session; + + g_variant_get (parameters, "(s)", &uri); + g_debug ("Creating session for service URI '%s'", uri); + + if (!tracker_util_parse_dbus_uri (uri, + &bus_type, + &service, + &object_path)) { + g_debug ("Could not parse URI '%s'", uri); + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Service cannot be parsed"); + return; + } + + dbus_connection = g_bus_get_sync (bus_type, NULL, &error); + + if (!dbus_connection) { + g_debug ("Could not get bus connection"); + g_dbus_method_invocation_return_gerror (invocation, error); + return; + } + + connection = tracker_sparql_connection_bus_new (service, object_path, dbus_connection, &error); + if (!connection) { + g_debug ("Could not stablish connection to service"); + g_dbus_method_invocation_return_gerror (invocation, error); + return; + } + + session_object_path = g_strdup_printf ("/org/freedesktop/portal/Tracker/Session_%" G_GUINT64_FORMAT, + portal->session_ids++); + + endpoint = TRACKER_ENDPOINT (tracker_endpoint_dbus_new (connection, + dbus_connection, + session_object_path, + NULL, + &error)); + if (!endpoint) { + g_debug ("Could not create endpoint"); + g_dbus_method_invocation_return_gerror (invocation, error); + return; + } + + session = (TrackerSession) { + .dbus_connection = g_steal_pointer (&dbus_connection), + .connection = g_steal_pointer (&connection), + .endpoint = g_steal_pointer (&endpoint), + .object_path = g_steal_pointer (&session_object_path), + }; + g_array_append_val (portal->sessions, session); + + g_debug ("Created session object '%s'", session.object_path); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(o)", session.object_path)); + } else { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method '%s'", method_name); + } +} + +static gboolean +tracker_portal_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + TrackerPortal *portal = TRACKER_PORTAL (initable); + + portal->node_info = g_dbus_node_info_new_for_xml (portal_xml, error); + if (!portal->node_info) + return FALSE; + + portal->register_id = + g_dbus_connection_register_object (portal->dbus_connection, + "/org/freedesktop/portal/Tracker", + portal->node_info->interfaces[0], + &(GDBusInterfaceVTable) { + portal_iface_method_call, + NULL, + NULL + }, + portal, + NULL, + error); + return TRUE; +} + +static void +tracker_portal_initable_iface_init (GInitableIface *iface) +{ + iface->init = tracker_portal_initable_init; +} + +TrackerPortal * +tracker_portal_new (GDBusConnection *connection, + GCancellable *cancellable, + GError **error) +{ + return g_initable_new (TRACKER_TYPE_PORTAL, cancellable, error, + "dbus-connection", connection, + NULL); +} diff --git a/src/portal/tracker-portal.h b/src/portal/tracker-portal.h new file mode 100644 index 000000000..dcf0b4e71 --- /dev/null +++ b/src/portal/tracker-portal.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + */ +#ifndef __TRACKER_PORTAL_H__ +#define __TRACKER_PORTAL_H__ + +#include <gio/gio.h> + +#define TRACKER_TYPE_PORTAL tracker_portal_get_type () +G_DECLARE_FINAL_TYPE (TrackerPortal, tracker_portal, TRACKER, PORTAL, GObject) + +TrackerPortal * tracker_portal_new (GDBusConnection *connection, + GCancellable *cancellable, + GError **error); + +#endif /* __TRACKER_PORTAL_H__ */ |