flatpak-builder/tests/test-doc-portal.c

428 lines
13 KiB
C

#include "config.h"
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "libglnx/libglnx.h"
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include "document-portal/xdp-dbus.h"
#include "xdg-app-dbus.h"
char outdir[] = "/tmp/xdp-test-XXXXXX";
GTestDBus *dbus;
GDBusConnection *session_bus;
XdpDbusDocuments *documents;
char *mountpoint;
static gboolean have_fuse;
static char *
make_doc_dir (const char *id, const char *app)
{
if (app)
return g_build_filename (mountpoint, "by-app", app, id, NULL);
else
return g_build_filename (mountpoint, id, NULL);
}
static char *
make_doc_path (const char *id, const char *basename, const char *app)
{
g_autofree char *dir = make_doc_dir (id, app);
return g_build_filename (dir, basename, NULL);
}
static void
assert_host_has_contents (const char *basename, const char *expected_contents)
{
g_autofree char *path = g_build_filename (outdir, basename, NULL);
g_autofree char *real_contents = NULL;
gsize real_contents_length;
GError *error = NULL;
g_file_get_contents (path, &real_contents, &real_contents_length, &error);
g_assert_no_error (error);
g_assert_cmpstr (real_contents, ==, expected_contents);
g_assert_cmpuint (real_contents_length, ==, strlen (expected_contents));
}
static void
assert_doc_has_contents (const char *id, const char *basename, const char *app, const char *expected_contents)
{
g_autofree char *path = make_doc_path (id, basename, app);
g_autofree char *real_contents = NULL;
gsize real_contents_length;
GError *error = NULL;
g_file_get_contents (path, &real_contents, &real_contents_length, &error);
g_assert_no_error (error);
g_assert_cmpstr (real_contents, ==, expected_contents);
g_assert_cmpuint (real_contents_length, ==, strlen (expected_contents));
}
static void
assert_doc_not_exist (const char *id, const char *basename, const char *app)
{
g_autofree char *path = make_doc_path (id, basename, app);
struct stat buf;
int res, fd;
res = stat (path, &buf);
g_assert_cmpint (res, ==, -1);
g_assert_cmpint (errno, ==, ENOENT);
fd = open (path, O_RDONLY);
g_assert_cmpint (fd, ==, -1);
g_assert_cmpint (errno, ==, ENOENT);
}
static char *
export_file (const char *path, gboolean unique)
{
int fd, fd_id;
GUnixFDList *fd_list = NULL;
g_autoptr(GVariant) reply = NULL;
GError *error = NULL;
char *doc_id;
fd = open (path, O_PATH | O_CLOEXEC);
g_assert (fd >= 0);
fd_list = g_unix_fd_list_new ();
fd_id = g_unix_fd_list_append (fd_list, fd, &error);
g_assert_no_error (error);
close (fd);
reply = g_dbus_connection_call_with_unix_fd_list_sync (session_bus,
"org.freedesktop.portal.Documents",
"/org/freedesktop/portal/documents",
"org.freedesktop.portal.Documents",
"Add",
g_variant_new ("(hbb)", fd_id, !unique, FALSE),
G_VARIANT_TYPE ("(s)"),
G_DBUS_CALL_FLAGS_NONE,
30000,
fd_list, NULL,
NULL,
&error);
g_object_unref (fd_list);
g_assert_no_error (error);
g_assert (reply != NULL);
g_variant_get (reply, "(s)", &doc_id);
g_assert (doc_id != NULL);
return doc_id;
}
static char *
export_new_file (const char *basename, const char *contents, gboolean unique)
{
g_autofree char *path = NULL;
g_autofree char *id = NULL;
g_autofree char *doc_path = NULL;
GError *error = NULL;
path = g_build_filename (outdir, basename, NULL);
g_file_set_contents (path, contents, -1, &error);
g_assert_no_error (error);
return export_file (path, unique);
}
static gboolean
update_doc (const char *id, const char *basename, const char *app, const char *contents, GError **error)
{
g_autofree char *path = make_doc_path (id, basename, app);
return g_file_set_contents (path, contents, -1, error);
}
static gboolean
update_from_host (const char *basename, const char *contents, GError **error)
{
g_autofree char *path = g_build_filename (outdir, basename, NULL);
return g_file_set_contents (path, contents, -1, error);
}
static void
grant_permissions (const char *id, const char *app, gboolean write)
{
g_autoptr(GPtrArray) permissions = g_ptr_array_new ();
GError *error = NULL;
g_ptr_array_add (permissions, "read");
if (write)
g_ptr_array_add (permissions, "write");
g_ptr_array_add (permissions, NULL);
xdp_dbus_documents_call_grant_permissions_sync (documents,
id,
app,
(const char **)permissions->pdata,
NULL,
&error);
g_assert_no_error (error);
}
static void
test_create_doc (void)
{
g_autofree char *doc_path = NULL;
g_autofree char *doc_app_path = NULL;
g_autofree char *host_path = NULL;
g_autofree char *id = NULL;
g_autofree char *id2 = NULL;
g_autofree char *id3 = NULL;
g_autofree char *id4 = NULL;
g_autofree char *id5 = NULL;
const char *basename = "a-file";
GError *error = NULL;
if (!have_fuse)
{
g_test_skip ("this test requires FUSE");
return;
}
/* Export a document */
id = export_new_file (basename, "content", FALSE);
/* Ensure its there and not viewable by apps */
assert_doc_has_contents (id, basename, NULL, "content");
assert_host_has_contents (basename, "content");
assert_doc_not_exist (id, basename, "com.test.App1");
assert_doc_not_exist (id, basename, "com.test.App2");
assert_doc_not_exist (id, "another-file", NULL);
assert_doc_not_exist ("anotherid", basename, NULL);
/* Create a tmp file in same dir, ensure it works and can't be seen by other apps */
assert_doc_not_exist (id, "tmp1", NULL);
update_doc (id, "tmp1", NULL, "tmpdata1", &error);
g_assert_no_error (error);
assert_doc_has_contents (id, "tmp1", NULL, "tmpdata1");
assert_doc_not_exist (id, "tmp1", "com.test.App1");
/* Let App 1 see the document (but not write) */
grant_permissions (id, "com.test.App1", FALSE);
/* Ensure App 1 and only it can see the document and tmpfile */
assert_doc_has_contents (id, basename, "com.test.App1", "content");
assert_doc_not_exist (id, basename, "com.test.App2");
assert_doc_not_exist (id, "tmp1", "com.test.App1");
/* Make sure App 1 can't create a tmpfile */
assert_doc_not_exist (id, "tmp2", "com.test.App1");
update_doc (id, "tmp2", "com.test.App1", "tmpdata2", &error);
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES);
g_clear_error (&error);
assert_doc_not_exist (id, "tmp2", "com.test.App1");
/* Update the document contents, ensure this is propagater */
update_doc (id, basename, NULL, "content2", &error);
g_assert_no_error (error);
assert_host_has_contents (basename, "content2");
assert_doc_has_contents (id, basename, NULL, "content2");
assert_doc_has_contents (id, basename, "com.test.App1", "content2");
assert_doc_not_exist (id, basename, "com.test.App2");
assert_doc_not_exist (id, "tmp1", "com.test.App2");
/* Update the document contents outside fuse fd, ensure this is propagater */
update_from_host (basename, "content3", &error);
g_assert_no_error (error);
assert_host_has_contents (basename, "content3");
assert_doc_has_contents (id, basename, NULL, "content3");
assert_doc_has_contents (id, basename, "com.test.App1", "content3");
assert_doc_not_exist (id, basename, "com.test.App2");
assert_doc_not_exist (id, "tmp1", "com.test.App2");
/* Try to update the doc from an app that can't write to it */
update_doc (id, basename, "com.test.App1", "content4", &error);
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES);
g_clear_error (&error);
/* Try to create a tmp file for an app that is not allowed */
assert_doc_not_exist (id, "tmp2", "com.test.App1");
update_doc (id, "tmp2", "com.test.App1", "tmpdata2", &error);
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES);
g_clear_error (&error);
assert_doc_not_exist (id, "tmp2", "com.test.App1");
assert_doc_not_exist (id, "tmp2", NULL);
/* Grant write permissions to App1 */
grant_permissions (id, "com.test.App1", TRUE);
/* update the doc from an app with write access */
update_doc (id, basename, "com.test.App1", "content5", &error);
g_assert_no_error (error);
assert_host_has_contents (basename, "content5");
assert_doc_has_contents (id, basename, NULL, "content5");
assert_doc_has_contents (id, basename, "com.test.App1", "content5");
assert_doc_not_exist (id, basename, "com.test.App2");
/* Try to create a tmp file for an app */
assert_doc_not_exist (id, "tmp3", "com.test.App1");
update_doc (id, "tmp3", "com.test.App1", "tmpdata3", &error);
g_assert_no_error (error);
assert_doc_has_contents (id, "tmp3", "com.test.App1", "tmpdata3");
assert_doc_not_exist (id, "tmp3", NULL);
/* Re-Create a file from a fuse document file, in various ways */
doc_path = make_doc_path (id, basename, NULL);
doc_app_path = make_doc_path (id, basename, "com.test.App1");
host_path = g_build_filename (outdir, basename, NULL);
id2 = export_file (doc_path, FALSE);
g_assert_cmpstr (id, ==, id2);
id3 = export_file (doc_app_path, FALSE);
g_assert_cmpstr (id, ==, id3);
id4 = export_file (host_path, FALSE);
g_assert_cmpstr (id, ==, id4);
/* Ensure we can make a unique document */
id5 = export_file (host_path, TRUE);
g_assert_cmpstr (id, !=, id5);
}
static void
test_recursive_doc (void)
{
g_autofree char *id = NULL;
g_autofree char *id2 = NULL;
g_autofree char *id3 = NULL;
const char *basename = "recursive-file";
g_autofree char *path = NULL;
g_autofree char *app_path = NULL;
if (!have_fuse)
{
g_test_skip ("this test requires FUSE");
return;
}
id = export_new_file (basename, "recursive-content", FALSE);
assert_doc_has_contents (id, basename, NULL, "recursive-content");
path = make_doc_path (id, basename, NULL);
g_print ("path: %s\n", path);
id2 = export_file (path, FALSE);
g_assert_cmpstr (id, ==, id2);
grant_permissions (id, "com.test.App1", FALSE);
app_path = make_doc_path (id, basename, "com.test.App1");
id3 = export_file (app_path, FALSE);
g_assert_cmpstr (id, ==, id3);
}
static void
global_setup (void)
{
gboolean inited;
g_autofree gchar *fusermount = NULL;
GError *error = NULL;
gint exit_status;
fusermount = g_find_program_in_path ("fusermount");
/* cache result so subsequent tests can be marked as skipped */
have_fuse = (access ("/dev/fuse", W_OK) == 0 &&
fusermount != NULL &&
g_file_test (fusermount, G_FILE_TEST_IS_EXECUTABLE));
if (!have_fuse)
return;
g_mkdtemp (outdir);
g_print ("outdir: %s\n", outdir);
g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE);
g_setenv ("XDG_DATA_HOME", outdir, TRUE);
dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
g_test_dbus_add_service_dir (dbus, TEST_SERVICES);
g_test_dbus_up (dbus);
/* g_test_dbus_up unsets this, so re-set */
g_setenv ("XDG_RUNTIME_DIR", outdir, TRUE);
g_spawn_command_line_sync (DOC_PORTAL " -d", NULL, NULL, &exit_status, &error);
g_assert_no_error (error);
g_assert_cmpint (exit_status, ==, 0);
session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_assert_no_error (error);
documents = xdp_dbus_documents_proxy_new_sync (session_bus, 0,
"org.freedesktop.portal.Documents",
"/org/freedesktop/portal/documents",
NULL, &error);
g_assert_no_error (error);
g_assert (documents != NULL);
inited = xdp_dbus_documents_call_get_mount_point_sync (documents, &mountpoint,
NULL, &error);
g_assert_no_error (error);
g_assert (inited);
g_assert (mountpoint != NULL);
}
static void
global_teardown (void)
{
GError *error = NULL;
if (!have_fuse)
return;
g_free (mountpoint);
g_object_unref (documents);
g_dbus_connection_close_sync (session_bus, NULL, &error);
g_assert_no_error (error);
g_object_unref (session_bus);
g_test_dbus_down (dbus);
g_object_unref (dbus);
/* We race on the unmount of the fuse fs, which causes the rm -rf to stop at the doc dir.
This makes the chance of completely removing the directory higher */
sleep (1);
glnx_shutil_rm_rf_at (-1, outdir, NULL, NULL);
}
int
main (int argc, char **argv)
{
int res;
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/db/create_doc", test_create_doc);
g_test_add_func ("/db/recursive_doc", test_recursive_doc);
global_setup ();
res = g_test_run ();
global_teardown ();
return res;
}