]> rtime.felk.cvut.cz Git - sojka/lightdm.git/commitdiff
Add shared data manager and test
authorMichael Terry <michael.terry@canonical.com>
Sat, 8 Feb 2014 23:36:27 +0000 (18:36 -0500)
committerMichael Terry <michael.terry@canonical.com>
Sat, 8 Feb 2014 23:36:27 +0000 (18:36 -0500)
debian/lightdm.dirs
src/Makefile.am
src/lightdm.c
src/shared-data-manager.c [new file with mode: 0644]
src/shared-data-manager.h [new file with mode: 0644]
tests/Makefile.am
tests/scripts/shared-data-dirs.conf [new file with mode: 0644]
tests/src/libsystem.c
tests/src/test-runner.c
tests/test-shared-data-dirs [new file with mode: 0755]

index 0bcf7abd420b54bd6a3e12189f299385e46aa0e1..255ef1e738705c24d5f5237e108ad2d2ef4de47c 100644 (file)
@@ -1,3 +1,4 @@
 /etc/X11
-/var/log/lightdm
 /var/cache/lightdm
+/var/lib/lightdm-data
+/var/log/lightdm
index 08222cf30d49cbb89c3630495214db79dcd0bc53..ba35eeaf638ce9b18d554a39fce0a421c452a833 100644 (file)
@@ -45,6 +45,8 @@ lightdm_SOURCES = \
        session-child.h \
        session-config.c \
        session-config.h \
+       shared-data-manager.c \
+       shared-data-manager.h \
        surfaceflinger-server.c \
        surfaceflinger-server.h \
        unity-system-compositor.c \
@@ -77,6 +79,7 @@ lightdm_CFLAGS = \
        -I"$(top_srcdir)/common" \
        -DSBIN_DIR=\"$(sbindir)\" \
        -DCONFIG_DIR=\"$(sysconfdir)/lightdm\" \
+       -DUSERS_DIR=\"$(localstatedir)/lib/lightdm-data\" \
        -DLOG_DIR=\"$(localstatedir)/log/lightdm\" \
        -DRUN_DIR=\"$(localstatedir)/run/lightdm\" \
        -DCACHE_DIR=\"$(localstatedir)/cache/lightdm\" \
index 42d47ee01d6622716cf7e08c5b2b13b5553b5d59..a45713dd01efb882bf67b4bb7714df36f71f09a4 100644 (file)
@@ -30,6 +30,7 @@
 #include "x-server.h"
 #include "process.h"
 #include "session-child.h"
+#include "shared-data-manager.h"
 #include "user-list.h"
 
 static gchar *config_path = NULL;
@@ -989,6 +990,7 @@ main (int argc, char **argv)
     gchar *default_cache_dir = g_strdup (CACHE_DIR);
     gboolean show_version = FALSE;
     GList *link, *messages = NULL;
+    SharedDataManager *shared_data_manager = NULL;
     GOptionEntry options[] =
     {
         { "config", 'c', 0, G_OPTION_ARG_STRING, &config_path,
@@ -1245,6 +1247,8 @@ main (int argc, char **argv)
     g_signal_connect (display_manager, "stopped", G_CALLBACK (display_manager_stopped_cb), NULL);
     g_signal_connect (display_manager, "seat-removed", G_CALLBACK (display_manager_seat_removed_cb), NULL);
 
+    shared_data_manager = shared_data_manager_new ();
+
     /* Load the static display entries */
     groups = config_get_groups (config_get_instance ());
     for (i = groups; *i; i++)
@@ -1320,6 +1324,10 @@ main (int argc, char **argv)
 
     g_main_loop_run (loop);
 
+    /* Clean up shared data manager */
+    g_object_unref (shared_data_manager);
+    shared_data_manager = NULL;
+
     /* Clean up user list */
     common_user_list_cleanup ();
 
diff --git a/src/shared-data-manager.c b/src/shared-data-manager.c
new file mode 100644 (file)
index 0000000..dbdc301
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2014 Canonical, Ltd
+ * Author: Michael Terry <michael.terry@canonical.com>
+ * 
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+#include <config.h>
+#include <gio/gio.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "configuration.h"
+#include "shared-data-manager.h"
+#include "user-list.h"
+
+#define NUM_ENUMERATION_FILES 100
+
+struct SharedDataManagerPrivate
+{
+    guint32 greeter_uid;
+    guint32 greeter_gid;
+    guint num_setup_users;
+    GHashTable *starting_dirs;
+};
+
+struct OwnerInfo
+{
+    SharedDataManager *manager;
+    guint32 uid;
+};
+
+G_DEFINE_TYPE (SharedDataManager, shared_data_manager, G_TYPE_OBJECT);
+
+SharedDataManager *
+shared_data_manager_new (void)
+{
+    return g_object_new (SHARED_DATA_MANAGER_TYPE, NULL);
+}
+
+static void
+delete_unused_user (gpointer key, gpointer value, gpointer user_data)
+{
+    const gchar *user = (const gchar *)key;
+    GError *error = NULL;
+
+    /* Listen, the rest of this file is nice async glib code and all, but
+       for this operation, we just need a fire and forget rm -rf.  Since
+       recursively deleting in GIO is a huge pain in the butt, we'll just drop
+       to shell for this. */
+
+    gchar *path = g_build_filename (USERS_DIR, user, NULL);
+    gchar *quoted_path = g_shell_quote (path);
+    gchar *cmd = g_strdup_printf ("/bin/rm -rf %s", quoted_path);
+
+    if (!g_spawn_command_line_async (cmd, &error))
+    {
+        g_warning ("Could not delete unused user data directory %s: %s", path, error->message);
+        g_error_free (error);
+    }
+
+    g_free (cmd);
+    g_free (quoted_path);
+    g_free (path);
+}
+
+static void
+chown_user_dir_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+    GFile *file = G_FILE (object);
+    GFileInfo *info = NULL;
+    GError *error = NULL;
+
+    if (!g_file_set_attributes_finish (file, res, &info, &error))
+    {
+        gchar *path = g_file_get_path (file);
+        g_warning ("Could not chown user data directory %s: %s",
+                   path, error->message);
+        g_free (path);
+        g_error_free (error);
+    }
+
+    if (info)
+        g_object_unref (info);
+}
+
+static void
+make_user_dir_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+    GFile *file = G_FILE (object);
+    struct OwnerInfo *owner = (struct OwnerInfo *)user_data;
+    GError *error = NULL;
+
+    if (!g_file_make_directory_finish (file, res, &error))
+    {
+        if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+        {
+            gchar *path = g_file_get_path (file);
+            g_warning ("Could not create user data directory %s: %s",
+                       path, error->message);
+            g_free (path);
+            g_error_free (error);
+            owner->manager->priv->num_setup_users--;
+            g_object_unref (owner->manager);
+            g_free (owner);
+            return;
+        }
+        g_error_free (error);
+    }
+
+    /* Even if the directory already exists, we want to re-affirm the owners
+       because the greeter gid is configuration based and may change between
+       runs. */
+    GFileInfo *info = g_file_info_new ();
+    g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID,
+                                      owner->uid);
+    g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID,
+                                      owner->manager->priv->greeter_gid);
+    g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, 0770);
+    g_file_set_attributes_async (file, info, G_FILE_QUERY_INFO_NONE,
+                                 G_PRIORITY_DEFAULT, NULL,
+                                 chown_user_dir_cb, NULL);
+
+    /* If we're the last user dir to be set up, delete unused user dirs */
+    owner->manager->priv->num_setup_users--;
+    if (owner->manager->priv->starting_dirs != NULL &&
+        owner->manager->priv->num_setup_users == 0)
+    {
+        g_hash_table_foreach (owner->manager->priv->starting_dirs,
+                              delete_unused_user, owner->manager);
+        g_hash_table_destroy (owner->manager->priv->starting_dirs);
+        owner->manager->priv->starting_dirs = NULL;
+    }
+
+    g_object_unref (owner->manager);
+    g_free (owner);
+}
+
+static void
+setup_user_dir (SharedDataManager *manager, guint32 uid)
+{
+    struct OwnerInfo *owner = g_malloc (sizeof (struct OwnerInfo));
+    owner->manager = g_object_ref (manager);
+    owner->uid = uid;
+
+    gchar *uidstr = g_strdup_printf ("%u", uid);
+    gchar *path = g_build_filename (USERS_DIR, uidstr, NULL);
+    GFile *file = g_file_new_for_path (path);
+    g_free (path);
+
+    manager->priv->num_setup_users++;
+    if (manager->priv->starting_dirs != NULL)
+        g_hash_table_remove (manager->priv->starting_dirs, uidstr);
+    g_free (uidstr);
+
+    g_file_make_directory_async (file, G_PRIORITY_DEFAULT, NULL,
+                                 make_user_dir_cb, owner);
+
+    g_object_unref (file);
+}
+
+static void
+next_user_dirs_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+    GFileEnumerator *enumerator = G_FILE_ENUMERATOR (object);
+    SharedDataManager *manager = SHARED_DATA_MANAGER (user_data);
+    GList *link;
+    GError *error = NULL;
+
+    GList *files = g_file_enumerator_next_files_finish (enumerator, res,
+                                                        &error);
+    if (error != NULL)
+    {
+        g_warning ("Could not enumerate user data directory %s: %s",
+                   USERS_DIR, error->message);
+        g_error_free (error);
+        g_object_unref (manager);
+        return;
+    }
+
+    for (link = files; link; link = link->next)
+    {
+        GFileInfo *info = link->data;
+        g_hash_table_insert (manager->priv->starting_dirs,
+                             g_strdup (g_file_info_get_name (info)), NULL);
+    }
+
+    if (files != NULL)
+    {
+        g_list_free_full (files, g_object_unref);
+        g_file_enumerator_next_files_async (enumerator, NUM_ENUMERATION_FILES,
+                                            G_PRIORITY_DEFAULT, NULL,
+                                            next_user_dirs_cb, manager);
+    }
+    else
+    {
+        // We've finally assembled all the initial directories.  Now let's
+        // iterate the current users and set them each up.  As we go, we'll
+        // remove the users from the starting_dirs hash and thus see which
+        // users are obsolete.
+        GList *users = common_user_list_get_users (common_user_list_get_instance ());
+        for (link = users; link; link = link->next)
+        {
+            CommonUser *user = link->data;
+            setup_user_dir (manager, common_user_get_uid (user));
+        }
+        // Also set up our own greeter dir, so it has a place to dump its own files
+        // (imagine it holding some large files temporarily before shunting them
+        // to the next user to log in's specific directory).
+        setup_user_dir (manager, manager->priv->greeter_uid);
+        g_object_unref (manager);
+    }
+}
+
+static void
+list_user_dirs_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+    GFile *file = G_FILE (object);
+    SharedDataManager *manager = SHARED_DATA_MANAGER (user_data);
+    GFileEnumerator *enumerator;
+    GError *error = NULL;
+
+    enumerator = g_file_enumerate_children_finish (file, res, &error);
+    if (enumerator == NULL)
+    {
+        g_warning ("Could not enumerate user data directory %s: %s",
+                   USERS_DIR, error->message);
+        g_error_free (error);
+        g_object_unref (manager);
+        return;
+    }
+
+    manager->priv->starting_dirs = g_hash_table_new_full (g_str_hash,
+                                                          g_str_equal,
+                                                          g_free, NULL);
+    g_file_enumerator_next_files_async (enumerator, NUM_ENUMERATION_FILES,
+                                        G_PRIORITY_DEFAULT, NULL,
+                                        next_user_dirs_cb, manager);
+}
+
+static void
+user_added_cb (CommonUserList *list, CommonUser *user,
+               SharedDataManager *manager)
+{
+    setup_user_dir (manager, common_user_get_uid (user));
+}
+
+static void
+user_removed_cb (CommonUserList *list, CommonUser *user,
+                 SharedDataManager *manager)
+{
+    gchar *uid = g_strdup_printf ("%u", common_user_get_uid (user));
+    delete_unused_user (uid, NULL, manager);
+    g_free (uid);
+}
+
+static void
+shared_data_manager_init (SharedDataManager *manager)
+{
+    manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, SHARED_DATA_MANAGER_TYPE, SharedDataManagerPrivate);
+
+    // Grab current greeter-user gid
+    gchar *greeter_user;
+    struct passwd *greeter_entry;
+    greeter_user = config_get_string (config_get_instance (), "LightDM", "greeter-user");
+    greeter_entry = getpwnam (greeter_user);
+    if (greeter_entry)
+    {
+        manager->priv->greeter_uid = greeter_entry->pw_uid;
+        manager->priv->greeter_gid = greeter_entry->pw_gid;
+    }
+    g_free (greeter_user);
+
+    /* Grab list of all current directories, so we know if any exist that we
+       no longer need. */
+    GFile *file = g_file_new_for_path (USERS_DIR);
+    g_file_enumerate_children_async (file, G_FILE_ATTRIBUTE_STANDARD_NAME,
+                                     G_FILE_QUERY_INFO_NONE,
+                                     G_PRIORITY_DEFAULT, NULL,
+                                     list_user_dirs_cb, g_object_ref (manager));
+    g_object_unref (file);
+
+    /* And listen for user changes.  The chance of a race with the above
+       initial setup is so tiny, it's not worth worrying about. */
+    g_signal_connect (common_user_list_get_instance (), "user-added",
+                      G_CALLBACK (user_added_cb), manager);
+    g_signal_connect (common_user_list_get_instance (), "user-removed",
+                      G_CALLBACK (user_removed_cb), manager);
+}
+
+static void
+shared_data_manager_dispose (GObject *object)
+{
+    SharedDataManager *self = SHARED_DATA_MANAGER (object);
+
+    /* Should also cancel outstanding GIO operations, but whatever, let them
+       do their thing. */
+
+    g_signal_handlers_disconnect_by_data (common_user_list_get_instance (),
+                                          self);
+
+    G_OBJECT_CLASS (shared_data_manager_parent_class)->dispose (object);
+}
+
+static void
+shared_data_manager_finalize (GObject *object)
+{
+    SharedDataManager *self = SHARED_DATA_MANAGER (object);
+
+    if (self->priv->starting_dirs)
+        g_hash_table_destroy (self->priv->starting_dirs);
+
+    G_OBJECT_CLASS (shared_data_manager_parent_class)->finalize (object);
+}
+
+static void
+shared_data_manager_class_init (SharedDataManagerClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    object_class->dispose = shared_data_manager_dispose;
+    object_class->finalize = shared_data_manager_finalize;
+
+    g_type_class_add_private (klass, sizeof (SharedDataManagerPrivate));
+}
diff --git a/src/shared-data-manager.h b/src/shared-data-manager.h
new file mode 100644 (file)
index 0000000..faa780a
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 Canonical, Ltd
+ * Author: Michael Terry <michael.terry@canonical.com>
+ * 
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+#ifndef SHARED_DATA_MANAGER_H_
+#define SHARED_DATA_MANAGER_H_
+
+#include <glib-object.h>
+
+typedef struct SharedDataManager SharedDataManager;
+
+G_BEGIN_DECLS
+
+#define SHARED_DATA_MANAGER_TYPE (shared_data_manager_get_type())
+#define SHARED_DATA_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHARED_DATA_MANAGER_TYPE, SharedDataManager))
+#define SHARED_DATA_MANAGER_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass), SHARED_DATA_MANAGER_TYPE, SharedDataManagerClass))
+#define SHARED_DATA_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHARED_DATA_MANAGER_TYPE, SharedDataManagerClass))
+
+typedef struct SharedDataManagerPrivate SharedDataManagerPrivate;
+
+struct SharedDataManager
+{
+    GObject                   parent_instance;
+    SharedDataManagerPrivate *priv;
+};
+
+typedef struct
+{
+    GObjectClass parent_class;
+} SharedDataManagerClass;
+
+GType shared_data_manager_get_type (void);
+
+SharedDataManager *shared_data_manager_new (void);
+
+G_END_DECLS
+
+#endif /* SHARED_DATA_MANAGER_H_ */
index 4b387d4a6d21bb217bb3f21706b896fbc0c27c1e..92ab7c4d75cd8131ce22ef690e182dc52820e4b8 100644 (file)
@@ -110,6 +110,7 @@ TESTS = \
        test-script-hook-fail-display-setup \
        test-script-hook-fail-greeter-setup \
        test-script-hook-fail-session-setup \
+       test-shared-data-dirs \
        test-upstart-autologin \
        test-upstart-login \
        test-dbus \
@@ -406,6 +407,7 @@ EXTRA_DIST = \
        scripts/plymouth-inactive-vt.conf \
        scripts/plymouth-no-seat.conf \
        scripts/restart-authentication.conf \
+       scripts/shared-data-dirs.conf \
        scripts/script-hooks.conf \
        scripts/script-hook-fail-display-setup.conf \
        scripts/script-hook-fail-greeter-setup.conf \
diff --git a/tests/scripts/shared-data-dirs.conf b/tests/scripts/shared-data-dirs.conf
new file mode 100644 (file)
index 0000000..141dc6e
--- /dev/null
@@ -0,0 +1,37 @@
+#
+# Make sure we manage shared user data directories on startup and over time
+#
+
+[test-runner-config]
+accounts-service-user-filter=have-password1 have-password2 have-password3
+# One normal, one with bad permissions, one to create, one to delete
+shared-data-dirs=1000:1000:100:0770 1001:1000:1000:0777 1004:1004:100:0770
+
+#?RUNNER DAEMON-START
+
+# X server starts
+#?XSERVER-0 START VT=7 SEAT=seat0
+
+# Startup creation/deletion
+#?*WAIT
+#?*LIST-SHARED-DATA-DIRS
+#?RUNNER LIST-SHARED-DATA-DIRS DIRS=100:100:100:0770,1000:1000:100:0770,1001:1001:100:0770,1002:1002:100:0770
+
+# Delete one user
+#?*DELETE-USER USERNAME=have-password1
+#?RUNNER DELETE-USER USERNAME=have-password1
+#?*WAIT
+#?*LIST-SHARED-DATA-DIRS
+#?RUNNER LIST-SHARED-DATA-DIRS DIRS=100:100:100:0770,1001:1001:100:0770,1002:1002:100:0770
+
+# Add one user
+#?*ADD-USER USERNAME=have-password4
+#?RUNNER ADD-USER USERNAME=have-password4
+#?*WAIT
+#?*LIST-SHARED-DATA-DIRS
+#?RUNNER LIST-SHARED-DATA-DIRS DIRS=100:100:100:0770,1001:1001:100:0770,1002:1002:100:0770,1003:1003:100:0770
+
+# Cleanup
+#?*STOP-DAEMON
+#?XSERVER-0 TERMINATE SIGNAL=15
+#?RUNNER DAEMON-EXIT STATUS=0
index 157bb993efe08e67606e2442a8d85a0c3454863c..b4db0c35310bba952b2c1b0c8a2e6402c1a165e0 100644 (file)
@@ -179,7 +179,7 @@ redirect_path (const gchar *path)
         return g_strdup (path);
 
     if (g_str_has_prefix (path, "/tmp"))
-        return g_build_filename (g_getenv ("LIGHTDM_TEST_ROOT"), "tmp", path + strlen ("tmp"), NULL);
+        return g_build_filename (g_getenv ("LIGHTDM_TEST_ROOT"), "tmp", path + strlen ("/tmp"), NULL);
 
     return g_strdup (path);
 }
@@ -255,6 +255,22 @@ fopen (const char *path, const char *mode)
     return result;
 }
 
+int
+unlinkat (int dirfd, const char *pathname, int flags)
+{
+    int (*_unlinkat) (int dirfd, const char *pathname, int flags);
+    gchar *new_path = NULL;
+    int result;
+
+    _unlinkat = (int (*)(int dirfd, const char *pathname, int flags)) dlsym (RTLD_NEXT, "unlinkat");
+
+    new_path = redirect_path (pathname);
+    result = _unlinkat (dirfd, new_path, flags);
+    g_free (new_path);
+
+    return result;
+}
+
 int
 creat (const char *pathname, mode_t mode)
 {
@@ -371,6 +387,38 @@ __xstat64 (int version, const char *path, struct stat64 *buf)
     return ret;
 }
 
+int
+__fxstatat(int ver, int dirfd, const char *pathname, struct stat *buf, int flags)
+{
+    int (*___fxstatat) (int ver, int dirfd, const char *pathname, struct stat *buf, int flags);
+    gchar *new_path = NULL;
+    int ret;
+  
+    ___fxstatat = (int (*)(int ver, int dirfd, const char *pathname, struct stat *buf, int flags)) dlsym (RTLD_NEXT, "__fxstatat");
+
+    new_path = redirect_path (pathname);
+    ret = ___fxstatat (ver, dirfd, new_path, buf, flags);
+    g_free (new_path);
+
+    return ret;
+}
+
+int
+__fxstatat64(int ver, int dirfd, const char *pathname, struct stat64 *buf, int flags)
+{
+    int (*___fxstatat64) (int ver, int dirfd, const char *pathname, struct stat64 *buf, int flags);
+    gchar *new_path = NULL;
+    int ret;
+  
+    ___fxstatat64 = (int (*)(int ver, int dirfd, const char *pathname, struct stat64 *buf, int flags)) dlsym (RTLD_NEXT, "__fxstatat64");
+
+    new_path = redirect_path (pathname);
+    ret = ___fxstatat64 (ver, dirfd, new_path, buf, flags);
+    g_free (new_path);
+
+    return ret;
+}
+
 DIR *
 opendir (const char *name)
 {
index c00a6290e755736b8c538da892d97dc34424d97f..fec53eae71a440be555a673909d40e2929261fb3 100644 (file)
@@ -319,6 +319,13 @@ get_script_line (const gchar *prefix)
     return NULL;
 }
 
+static gboolean
+stop_loop (gpointer user_data)
+{
+    g_main_loop_quit ((GMainLoop *)user_data);
+    return G_SOURCE_REMOVE;
+}
+
 static void
 handle_command (const gchar *command)
 {
@@ -398,7 +405,50 @@ handle_command (const gchar *command)
 
     if (strcmp (name, "WAIT") == 0)
     {
-        sleep (1);
+        /* Use a main loop so that our DBus functions are still responsive */
+        GMainLoop *loop = g_main_loop_new (NULL, FALSE);
+        g_timeout_add_seconds (1, stop_loop, loop);
+        g_main_loop_run (loop);
+        g_main_loop_unref (loop);
+    }
+    else if (strcmp (name, "LIST-SHARED-DATA-DIRS") == 0)
+    {
+        gchar *shared_dir;
+        GDir *dir;
+        const gchar *path;
+        GList *paths = NULL, *link;
+        GString *status;
+
+        shared_dir = g_strdup_printf ("%s/var/lib/lightdm-data", temp_dir);
+        dir = g_dir_open (shared_dir, 0, NULL);
+        while ((path = g_dir_read_name (dir)))
+        {
+            gchar *full_path = g_build_filename (shared_dir, path, NULL);
+            paths = g_list_insert_sorted (paths, full_path, (GCompareFunc)g_strcmp0);
+        }
+        g_dir_close (dir);
+        g_free (shared_dir);
+
+        status = g_string_new ("RUNNER LIST-SHARED-DATA-DIRS DIRS=");
+        for (link = paths; link; link = link->next)
+        {
+            path = (const gchar *)link->data;
+            GStatBuf buf;
+            if (g_stat (path, &buf) != 0)
+                continue;
+
+            if (link != paths)
+                g_string_append (status, ",");
+            gchar *basename = g_path_get_basename (path);
+            g_string_append_printf (status, "%s:%u:%u:0%o", basename,
+                                    buf.st_uid, buf.st_gid,
+                                    buf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO));
+            g_free (basename);
+        }
+        g_list_free_full (paths, g_free);
+
+        check_status (status->str);
+        g_string_free (status, TRUE);
     }
     else if (strcmp (name, "LIST-SEATS") == 0)
     {
@@ -2046,6 +2096,7 @@ main (int argc, char **argv)
     g_mkdir_with_parents (g_strdup_printf ("%s/usr/share/lightdm/remote-sessions", temp_dir), 0755);
     g_mkdir_with_parents (g_strdup_printf ("%s/usr/share/lightdm/greeters", temp_dir), 0755);
     g_mkdir_with_parents (g_strdup_printf ("%s/tmp", temp_dir), 0755);
+    g_mkdir_with_parents (g_strdup_printf ("%s/var/lib/lightdm-data", temp_dir), 0755);
     g_mkdir_with_parents (g_strdup_printf ("%s/var/run", temp_dir), 0755);
     g_mkdir_with_parents (g_strdup_printf ("%s/var/log", temp_dir), 0755);
 
@@ -2083,6 +2134,37 @@ main (int argc, char **argv)
         g_strfreev (files);
     }
 
+    if (g_key_file_has_key (config, "test-runner-config", "shared-data-dirs", NULL))
+    {
+        gchar *dir_string;
+        gchar **dirs;
+        gint i;
+
+        dir_string = g_key_file_get_string (config, "test-runner-config", "shared-data-dirs", NULL);
+        dirs = g_strsplit (dir_string, " ", -1);
+        g_free (dir_string);
+
+        for (i = 0; dirs[i]; i++)
+        {
+            gchar **fields = g_strsplit (dirs[i], ":", -1);
+            if (g_strv_length (fields) == 4)
+            {
+                gchar *path = g_strdup_printf ("%s/var/lib/lightdm-data/%s", temp_dir, fields[0]);
+                int uid = g_ascii_strtoll (fields[1], NULL, 10);
+                int gid = g_ascii_strtoll (fields[2], NULL, 10);
+                int mode = g_ascii_strtoll (fields[3], NULL, 8);
+                g_mkdir (path, mode);
+                g_chmod (path, mode); /* mkdir filters by umask, so make sure we have what we want */
+                if (chown (path, uid, gid) < 0)
+                  g_warning ("chown (%s) failed: %s", path, strerror (errno));
+                g_free (path);
+            }
+            g_strfreev (fields);
+        }
+
+        g_strfreev (dirs);
+    }
+
     /* Always copy the script */
     if (system (g_strdup_printf ("cp %s %s/script", config_path, temp_dir)))
         perror ("Failed to copy configuration");
diff --git a/tests/test-shared-data-dirs b/tests/test-shared-data-dirs
new file mode 100755 (executable)
index 0000000..e4c475e
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+./src/dbus-env ./src/test-runner shared-data-dirs test-gobject-greeter