Skip to content

Commit

Permalink
document-portal: Add AddFull() operation
Browse files Browse the repository at this point in the history
This allows you to add multiple paths at the same time, plus
grant an app access to it, plus it returns the fuse mount path.

This allows you to avoid a lot of roundtrip in common cases.
  • Loading branch information
alexlarsson committed May 19, 2017
1 parent bda7575 commit 6ce8521
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 30 deletions.
26 changes: 26 additions & 0 deletions data/org.freedesktop.portal.Documents.xml
Expand Up @@ -96,6 +96,32 @@
<arg type='s' name='doc_id' direction='out'/>
</method>

<!--
AddFull:
@o_path_fds: open file descriptors for the files to export
@flags: flags, 1 == reuse_existing, 2 == persistent
@app_id: an application ID, or empty string
@permissions: the permissions to grant, possible values are 'read', 'write', 'grant-permissions' and 'delete'
@doc_ids: the IDs of the files in the document store
@extra_info: Extra info returned
Adds multiple files to the document store. The file is passed in the
form of an open file descriptor to prove that the caller has
access to the file.
Additionally, if app_id is specified, it will be given the permissions
listed in GrantPermission.
The method also returns some extra info that can be used to avoid
multiple roundtrips. For now it only contains as "mountpoint", the
fuse mountpoint of the document portal.
-->
<method name="AddFull">
<arg type='ah' name='o_path_fds' direction='in'/>
<arg type='u' name='flags' direction='in'/>
<arg type='s' name='app_id' direction='in'/>
<arg type='as' name='permissions' direction='in'/>
<arg type='as' name='doc_ids' direction='out'/>
<arg type='a{sv}' name='extra_out' direction='out'/>
</method>

<!--
GrantPermissions:
@doc_id: the ID of the file in the document store
Expand Down
1 change: 1 addition & 0 deletions document-portal/Makefile.am.inc
Expand Up @@ -12,6 +12,7 @@ $(xdp_dbus_built_sources) : data/org.freedesktop.portal.Documents.xml
--c-namespace XdpDbus \
--generate-c-code $(builddir)/document-portal/xdp-dbus \
--annotate "org.freedesktop.portal.Documents.Add()" org.gtk.GDBus.C.UnixFD "yes" \
--annotate "org.freedesktop.portal.Documents.AddFull()" org.gtk.GDBus.C.UnixFD "yes" \
$(srcdir)/data/org.freedesktop.portal.Documents.xml \
$(NULL)

Expand Down
7 changes: 7 additions & 0 deletions document-portal/xdp-enums.h
Expand Up @@ -12,6 +12,13 @@ typedef enum {
XDP_PERMISSION_FLAGS_ALL = ((1 << 4) - 1)
} XdpPermissionFlags;

typedef enum {
XDP_ADD_FLAGS_REUSE_EXISTING = (1 << 0),
XDP_ADD_FLAGS_PERSISTENT = (1 << 1),

XDP_ADD_FLAGS_FLAGS_ALL = ((1 << 2) - 1)
} XdpAddFullFlags;

G_END_DECLS

#endif /* XDP_ENUMS_H */
210 changes: 180 additions & 30 deletions document-portal/xdp-main.c
Expand Up @@ -452,6 +452,36 @@ validate_fd (int fd,
return TRUE;
}

static char *
verify_existing_document (struct stat *st_buf, gboolean reuse_existing)
{
g_autoptr(FlatpakDbEntry) old_entry = NULL;
g_autofree char *id = NULL;

g_assert (st_buf->st_dev == fuse_dev);

/* The passed in fd is on the fuse filesystem itself */
id = xdp_fuse_lookup_id_for_inode (st_buf->st_ino);
g_debug ("path on fuse, id %s", id);
if (id == NULL)
return NULL;

/* Don't lock the db before doing the fuse call above, because it takes takes a lock
that can block something calling back, causing a deadlock on the db lock */
AUTOLOCK (db);

/* If the entry doesn't exist anymore, fail. Also fail if not
* reuse_existing, because otherwise the user could use this to
* get a copy with permissions and thus escape later permission
* revocations
*/
old_entry = flatpak_db_lookup (db, id);
if (old_entry == NULL || !reuse_existing)
return NULL;

return g_steal_pointer (&id);
}

static void
portal_add (GDBusMethodInvocation *invocation,
GVariant *parameters,
Expand Down Expand Up @@ -490,37 +520,14 @@ portal_add (GDBusMethodInvocation *invocation,
if (st_buf.st_dev == fuse_dev)
{
/* The passed in fd is on the fuse filesystem itself */
g_autoptr(FlatpakDbEntry) old_entry = NULL;

id = xdp_fuse_lookup_id_for_inode (st_buf.st_ino);
g_debug ("path on fuse, id %s", id);
id = verify_existing_document (&st_buf, reuse_existing);
if (id == NULL)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid fd passed");
return;
}

/* Don't lock the db before doing the fuse call above, because it takes takes a lock
that can block something calling back, causing a deadlock on the db lock */

AUTOLOCK (db);

/* If the entry doesn't exist anymore, fail. Also fail if not
* reuse_existing, because otherwise the user could use this to
* get a copy with permissions and thus escape later permission
* revocations
*/
old_entry = flatpak_db_lookup (db, id);
if (old_entry == NULL ||
!reuse_existing)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid fd passed");
return;
}
}
else
{
Expand All @@ -531,13 +538,136 @@ portal_add (GDBusMethodInvocation *invocation,

if (app_id[0] != '\0')
{
g_autoptr(FlatpakDbEntry) entry = NULL;
g_autoptr(FlatpakDbEntry) entry = flatpak_db_lookup (db, id);
XdpPermissionFlags perms =
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS |
XDP_PERMISSION_FLAGS_READ |
XDP_PERMISSION_FLAGS_WRITE;

/* If its a unique one its safe for the creator to
delete it at will */
if (!reuse_existing)
perms |= XDP_PERMISSION_FLAGS_DELETE;

do_set_permissions (entry, id, app_id, perms);
}
}

/* Invalidate with lock dropped to avoid deadlock */
xdp_fuse_invalidate_doc_app (id, NULL);
if (app_id[0] != '\0')
xdp_fuse_invalidate_doc_app (id, app_id);
}

g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(s)", id));
}

static void
portal_add_full (GDBusMethodInvocation *invocation,
GVariant *parameters,
const char *app_id)
{
GDBusMessage *message;
GUnixFDList *fd_list;
char *id;
int fd_id, fd, fds_len;
char path_buffer[PATH_MAX + 1];
const int *fds = NULL;
struct stat st_buf;
gboolean reuse_existing, persistent;
GError *error = NULL;
guint32 flags = 0;
GKeyFile *app_info = g_object_get_data (G_OBJECT (invocation), "app-info");
g_autoptr(GVariant) array = NULL;
const char *target_app_id;
g_autofree const char **permissions = NULL;
g_autofree guint32 *arg_fds = NULL;
g_autoptr(GPtrArray) ids = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) paths = g_ptr_array_new_with_free_func (g_free);
g_autofree struct stat *real_parent_st_bufs = NULL;
int i;
gsize n_args;
XdpPermissionFlags target_perms;
GVariantBuilder builder;

g_variant_get (parameters, "(@ahus^a&s)",
&array, &flags, &target_app_id, &permissions);

if ((flags & ~XDP_ADD_FLAGS_FLAGS_ALL) != 0)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid flags");
return;
}

reuse_existing = (flags & XDP_ADD_FLAGS_REUSE_EXISTING) != 0;
persistent = (flags & XDP_ADD_FLAGS_PERSISTENT) != 0;

target_perms = xdp_parse_permissions (permissions);

n_args = g_variant_n_children (array);
g_ptr_array_set_size (ids, n_args + 1);
g_ptr_array_set_size (paths, n_args + 1);
real_parent_st_bufs = g_new0 (struct stat, n_args);

message = g_dbus_method_invocation_get_message (invocation);
fd_list = g_dbus_message_get_unix_fd_list (message);
if (fd_list != NULL)
fds = g_unix_fd_list_peek_fds (fd_list, &fds_len);

for (i = 0; i < n_args; i++)
{
g_variant_get_child (array, i, "h", &fd_id);

fd = -1;
if (fds != NULL & fd_id < fds_len)
fd = fds[fd_id];

if (!validate_fd (fd, app_info, &st_buf, &real_parent_st_bufs[i], path_buffer, &error))
{
g_dbus_method_invocation_take_error (invocation, error);
return;
}

if (st_buf.st_dev == fuse_dev)
{
/* The passed in fd is on the fuse filesystem itself */
id = verify_existing_document (&st_buf, reuse_existing);
if (id == NULL)
{
g_dbus_method_invocation_return_error (invocation,
FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT,
"Invalid fd passed");
return;
}
g_ptr_array_index(ids,i) = id;
}
else
{
g_ptr_array_index(paths,i) = g_strdup (path_buffer);
}
}

for (i = 0; i < n_args; i++)
{
AUTOLOCK (db);

if (g_ptr_array_index(ids,i) == NULL)
{
const char *path = g_ptr_array_index(paths,i);
g_assert (path != NULL);

id = do_create_doc (&real_parent_st_bufs[i], path, reuse_existing, persistent);
g_ptr_array_index(ids,i) = id;
if (app_id[0] != '\0' && strcmp (app_id, target_app_id) != 0)
{
entry = flatpak_db_lookup (db, id);
g_autoptr(FlatpakDbEntry) entry = flatpak_db_lookup (db, id);;
XdpPermissionFlags perms =
XDP_PERMISSION_FLAGS_GRANT_PERMISSIONS |
XDP_PERMISSION_FLAGS_READ |
XDP_PERMISSION_FLAGS_WRITE;

/* If its a unique one its safe for the creator to
delete it at will */
Expand All @@ -546,17 +676,36 @@ portal_add (GDBusMethodInvocation *invocation,

do_set_permissions (entry, id, app_id, perms);
}
}
}

/* Invalidate with lock dropped to avoid deadlock */
if (target_app_id[0] != '\0' && target_perms != 0)
{
g_autoptr(FlatpakDbEntry) entry = flatpak_db_lookup (db, id);
do_set_permissions (entry, id, target_app_id, target_perms);
}
}
}

/* Invalidate with lock dropped to avoid deadlock */
for (i = 0; i < n_args; i++)
{
id = g_ptr_array_index (ids,i);
g_assert (id != NULL);

xdp_fuse_invalidate_doc_app (id, NULL);
if (app_id[0] != '\0')
xdp_fuse_invalidate_doc_app (id, app_id);
if (target_app_id[0] != '\0' && target_perms != 0)
xdp_fuse_invalidate_doc_app (id, target_app_id);
}

g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{s@v}", "mountpoint",
g_variant_new_variant (g_variant_new_string (xdp_fuse_get_mountpoint ())));

g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(s)", id));
g_variant_new ("(^as@a{sv})",
(char **)ids->pdata,
g_variant_builder_end (&builder)));
}

static void
Expand Down Expand Up @@ -878,6 +1027,7 @@ on_bus_acquired (GDBusConnection *connection,
g_signal_connect_swapped (dbus_api, "handle-get-mount-point", G_CALLBACK (handle_get_mount_point), NULL);
g_signal_connect_swapped (dbus_api, "handle-add", G_CALLBACK (handle_method), portal_add);
g_signal_connect_swapped (dbus_api, "handle-add-named", G_CALLBACK (handle_method), portal_add_named);
g_signal_connect_swapped (dbus_api, "handle-add-full", G_CALLBACK (handle_method), portal_add_full);
g_signal_connect_swapped (dbus_api, "handle-grant-permissions", G_CALLBACK (handle_method), portal_grant_permissions);
g_signal_connect_swapped (dbus_api, "handle-revoke-permissions", G_CALLBACK (handle_method), portal_revoke_permissions);
g_signal_connect_swapped (dbus_api, "handle-delete", G_CALLBACK (handle_method), portal_delete);
Expand Down

0 comments on commit 6ce8521

Please sign in to comment.