summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Hergert <chergert@redhat.com>2019-04-08 12:06:44 -0700
committerChristian Hergert <chergert@redhat.com>2019-04-08 12:07:53 -0700
commit455e6221219b214888943bd462a33555bfe138f6 (patch)
treecc257ce5703ab8d3cdc3f66c80b86e2903032752
parent7a399e859d80d5d8ae1be9b119dc43a2f2eebe93 (diff)
downloadglib-wip/chergert/enumerate-children.tar.gz
gio: add g_file_enumerator_enumerate_children()wip/chergert/enumerate-children
This adds the necessary functions to create an emumerator to enumerate children directly from another enumerator. Doing so allows for the use of openat() using the directory FD. This ensures that enumeration may still succeed even though a parent directory could have been moved in the process. It also may potentially reduce the overhead on accessing the child directory handle on some file-systems. An implementation for GLocalFile is provided when on unix systems supporting openat() with O_DIRECTORY. It may be interesting for GFileEnumerator to gain additional operations such as move, delete, or create in the future so that the directory fd may be used there as well. Requiring the round-trip to a GFile can be an inherent race. However, to do this, we probably want additional file operation interfaces that can be implemented by enumerators as we will run out of padding quickly. Fixes #1745
-rw-r--r--docs/reference/gio/gio-sections.txt3
-rw-r--r--gio/gfileenumerator.c228
-rw-r--r--gio/gfileenumerator.h41
-rw-r--r--gio/glocalfileenumerator.c251
-rw-r--r--gio/tests/enumerator-children.c205
-rw-r--r--gio/tests/meson.build1
6 files changed, 705 insertions, 24 deletions
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 6aa07b462..1b3e7c2fa 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -249,6 +249,9 @@ g_file_enumerator_has_pending
g_file_enumerator_set_pending
g_file_enumerator_get_container
g_file_enumerator_get_child
+g_file_enumerator_enumerate_children
+g_file_enumerator_enumerate_children_async
+g_file_enumerator_enumerate_children_finish
<SUBSECTION Standard>
GFileEnumeratorClass
G_FILE_ENUMERATOR
diff --git a/gio/gfileenumerator.c b/gio/gfileenumerator.c
index d96a798af..564c6d90e 100644
--- a/gio/gfileenumerator.c
+++ b/gio/gfileenumerator.c
@@ -94,6 +94,23 @@ static void g_file_enumerator_real_close_async (GFileEnumerator *
static gboolean g_file_enumerator_real_close_finish (GFileEnumerator *enumerator,
GAsyncResult *res,
GError **error);
+static GFileEnumerator *g_file_enumerator_real_enumerate_children (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+static void g_file_enumerator_real_enumerate_children_async (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+static GFileEnumerator *g_file_enumerator_real_enumerate_children_finish (GFileEnumerator *enumerator,
+ GAsyncResult *result,
+ GError **error);
static void
g_file_enumerator_set_property (GObject *object,
@@ -156,6 +173,9 @@ g_file_enumerator_class_init (GFileEnumeratorClass *klass)
klass->next_files_finish = g_file_enumerator_real_next_files_finish;
klass->close_async = g_file_enumerator_real_close_async;
klass->close_finish = g_file_enumerator_real_close_finish;
+ klass->enumerate_children = g_file_enumerator_real_enumerate_children;
+ klass->enumerate_children_async = g_file_enumerator_real_enumerate_children_async;
+ klass->enumerate_children_finish = g_file_enumerator_real_enumerate_children_finish;
g_object_class_install_property
(gobject_class, PROP_CONTAINER,
@@ -867,3 +887,211 @@ g_file_enumerator_real_close_finish (GFileEnumerator *enumerator,
return g_task_propagate_boolean (G_TASK (result), error);
}
+/**
+ * g_file_enumerator_enumerate_children:
+ * @enumerator: a #GFileEnumerator
+ * @child_name: the name of directory within @enumerator to enumerate
+ * @attributes: an attribute query string
+ * @flags: a set of #GFileQueryInfoFlags
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @error: a location for a #GError, or %NULL
+ *
+ * Requests the enumeration of directory named @child_name that can be found
+ * within the directory represented by @enumerator.
+ *
+ * This is similar to calling g_file_enumerate_children() except that in some
+ * situations the POSIX openat() syscall may be used to avoid a race with
+ * rename() causing the enumerated directory to have moved.
+ *
+ * Additionally, the use of openat() may provide reduced overhead under
+ * certain situations.
+ *
+ * See g_file_enumerator_enumerate_children_async() for the asynchronous
+ * version of this function.
+ *
+ * Since: 2.62
+ */
+GFileEnumerator *
+g_file_enumerator_enumerate_children (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), NULL);
+ g_return_val_if_fail (child_name != NULL, NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+
+ return G_FILE_ENUMERATOR_GET_CLASS (enumerator)->enumerate_children (enumerator,
+ child_name,
+ attributes,
+ flags,
+ cancellable,
+ error);
+}
+
+/**
+ * g_file_enumerator_enumerate_children_async:
+ * @enumerator: a #GFileEnumerator
+ * @child_name: the name of directory within @enumerator to enumerate
+ * @attributes: an attribute query string
+ * @flags: a set of #GFileQueryInfoFlags
+ * @io_priority: the IO priority for the operation, or %G_PRIORITY_DEFAULT
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: a callback to execute upon completion of the operation
+ * @user_data: closure data for @callback
+ *
+ * Asynchronously requests the enumeration of directory named @child_name
+ * that can be found within the directory represented by @enumerator.
+ *
+ * This is similar to calling g_file_enumerate_children() except that in some
+ * situations the POSIX openat() syscall may be used to avoid a race with
+ * rename() causing the enumerated directory to have moved.
+ *
+ * Additionally, the use of openat() may provide reduced overhead under
+ * certain situations.
+ *
+ * See g_file_enumerator_enumerate_children() for the synchronous version
+ * of this function.
+ *
+ * Since: 2.62
+ */
+void
+g_file_enumerator_enumerate_children_async (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (G_IS_FILE_ENUMERATOR (enumerator));
+ g_return_if_fail (child_name != NULL);
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ G_FILE_ENUMERATOR_GET_CLASS (enumerator)->enumerate_children_async (enumerator,
+ child_name,
+ attributes,
+ flags,
+ io_priority,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * g_file_enumerator_enumerate_children_finish:
+ * @self: a #GFileEnumerator
+ * @result: a #GAsyncResult provided to callback
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to
+ * g_file_enumerator_enumerate_children_async().
+ *
+ * Returns: (transfer full): a #GFileEnumerator if successful; otherwise %FALSE
+ *
+ * Since: 2.62
+ */
+GFileEnumerator *
+g_file_enumerator_enumerate_children_finish (GFileEnumerator *enumerator,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+ return G_FILE_ENUMERATOR_GET_CLASS (enumerator)->enumerate_children_finish (enumerator,
+ result,
+ error);
+}
+
+static GFileEnumerator *
+g_file_enumerator_real_enumerate_children (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GFileEnumerator *ret = NULL;
+ GFile *file;
+
+ g_assert (G_IS_FILE_ENUMERATOR (enumerator));
+ g_assert (child_name != NULL);
+ g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ file = g_file_get_child (enumerator->priv->container, child_name);
+ ret = g_file_enumerate_children (file, attributes, flags, cancellable, error);
+ g_clear_object (&file);
+
+ return g_steal_pointer (&ret);
+}
+
+static void
+g_file_enumerator_real_enumerate_children_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *file = (GFile *)object;
+ GError *error = NULL;
+ GTask *task = user_data;
+ GFileEnumerator *ret;
+
+ g_assert (G_IS_FILE (file));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (G_IS_TASK (task));
+
+ ret = g_file_enumerate_children_finish (file, result, &error);
+
+ if (ret != NULL)
+ g_task_return_pointer (task, g_steal_pointer (&ret), g_object_unref);
+ else
+ g_task_return_error (task, g_steal_pointer (&error));
+
+ g_clear_object (&task);
+}
+
+static void
+g_file_enumerator_real_enumerate_children_async (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GFile *file;
+
+ g_assert (G_IS_FILE_ENUMERATOR (enumerator));
+ g_assert (child_name != NULL);
+ g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (enumerator, cancellable, callback, user_data);
+ g_task_set_source_tag (task, g_file_enumerator_real_enumerate_children_async);
+ g_task_set_priority (task, io_priority);
+
+ file = g_file_get_child (enumerator->priv->container, child_name);
+ g_file_enumerate_children_async (file,
+ attributes,
+ flags,
+ io_priority,
+ cancellable,
+ g_file_enumerator_real_enumerate_children_cb,
+ g_steal_pointer (&task));
+ g_clear_object (&file);
+}
+
+static GFileEnumerator *
+g_file_enumerator_real_enumerate_children_finish (GFileEnumerator *enumerator,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (G_IS_FILE_ENUMERATOR (enumerator));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/gio/gfileenumerator.h b/gio/gfileenumerator.h
index d4fd396b6..29db736f1 100644
--- a/gio/gfileenumerator.h
+++ b/gio/gfileenumerator.h
@@ -82,12 +82,26 @@ struct _GFileEnumeratorClass
gboolean (* close_finish) (GFileEnumerator *enumerator,
GAsyncResult *result,
GError **error);
+ GFileEnumerator *(* enumerate_children) (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+ void (* enumerate_children_async) (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ GFileEnumerator *(* enumerate_children_finish) (GFileEnumerator *enumerator,
+ GAsyncResult *result,
+ GError **error);
/*< private >*/
/* Padding for future expansion */
- void (*_g_reserved1) (void);
- void (*_g_reserved2) (void);
- void (*_g_reserved3) (void);
void (*_g_reserved4) (void);
void (*_g_reserved5) (void);
void (*_g_reserved6) (void);
@@ -145,7 +159,26 @@ gboolean g_file_enumerator_iterate (GFileEnumerator *direnum,
GFile **out_child,
GCancellable *cancellable,
GError **error);
-
+GLIB_AVAILABLE_IN_2_62
+GFileEnumerator *g_file_enumerator_enumerate_children (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+GLIB_AVAILABLE_IN_2_62
+void g_file_enumerator_enumerate_children_async (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GLIB_AVAILABLE_IN_2_62
+GFileEnumerator *g_file_enumerator_enumerate_children_finish (GFileEnumerator *enumerator,
+ GAsyncResult *result,
+ GError **error);
G_END_DECLS
diff --git a/gio/glocalfileenumerator.c b/gio/glocalfileenumerator.c
index 4f316f7ea..88fe7f88c 100644
--- a/gio/glocalfileenumerator.c
+++ b/gio/glocalfileenumerator.c
@@ -20,6 +20,7 @@
#include "config.h"
+#include <fcntl.h>
#include <glib.h>
#include <glocalfileenumerator.h>
#include <glocalfileinfo.h>
@@ -27,7 +28,9 @@
#include <gioerror.h>
#include <string.h>
#include <stdlib.h>
+#include <unistd.h>
#include "glibintl.h"
+#include "gtask.h"
#define CHUNK_SIZE 1000
@@ -84,7 +87,23 @@ static GFileInfo *g_local_file_enumerator_next_file (GFileEnumerator *enumerato
static gboolean g_local_file_enumerator_close (GFileEnumerator *enumerator,
GCancellable *cancellable,
GError **error);
-
+static GFileEnumerator *g_local_file_enumerator_enumerate_children (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+static void g_local_file_enumerator_enumerate_children_async (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+static GFileEnumerator *g_local_file_enumerator_enumerate_children_finish (GFileEnumerator *enumerator,
+ GAsyncResult *result,
+ GError **error);
static void
free_entries (GLocalFileEnumerator *local)
@@ -140,6 +159,9 @@ g_local_file_enumerator_class_init (GLocalFileEnumeratorClass *klass)
enumerator_class->next_file = g_local_file_enumerator_next_file;
enumerator_class->close_fn = g_local_file_enumerator_close;
+ enumerator_class->enumerate_children = g_local_file_enumerator_enumerate_children;
+ enumerator_class->enumerate_children_async = g_local_file_enumerator_enumerate_children_async;
+ enumerator_class->enumerate_children_finish = g_local_file_enumerator_enumerate_children_finish;
}
static void
@@ -199,23 +221,54 @@ g_file_attribute_matcher_subtract_attributes (GFileAttributeMatcher *matcher,
}
#endif
+static GFileEnumerator *
+_g_local_file_enumerator_new_with_dir (GLocalFile *file,
+#ifdef USE_GDIR
+ GDir *dir,
+#else
+ DIR *dir,
+#endif
+ const char *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GLocalFileEnumerator *local;
+ char *filename = g_file_get_path (G_FILE (file));
+
+ local = g_object_new (G_TYPE_LOCAL_FILE_ENUMERATOR,
+ "container", file,
+ NULL);
+
+ local->dir = dir;
+ local->filename = filename;
+ local->matcher = g_file_attribute_matcher_new (attributes);
+#ifndef USE_GDIR
+ local->reduced_matcher = g_file_attribute_matcher_subtract_attributes (local->matcher,
+ G_LOCAL_FILE_INFO_NOSTAT_ATTRIBUTES","
+ "standard::type");
+#endif
+ local->flags = flags;
+
+ return G_FILE_ENUMERATOR (local);
+}
+
GFileEnumerator *
-_g_local_file_enumerator_new (GLocalFile *file,
+_g_local_file_enumerator_new (GLocalFile *file,
const char *attributes,
GFileQueryInfoFlags flags,
GCancellable *cancellable,
GError **error)
{
- GLocalFileEnumerator *local;
char *filename = g_file_get_path (G_FILE (file));
#ifdef USE_GDIR
GError *dir_error;
GDir *dir;
-
+
dir_error = NULL;
dir = g_dir_open (filename, 0, error != NULL ? &dir_error : NULL);
- if (dir == NULL)
+ if (dir == NULL)
{
if (error != NULL)
{
@@ -246,22 +299,8 @@ _g_local_file_enumerator_new (GLocalFile *file,
}
#endif
-
- local = g_object_new (G_TYPE_LOCAL_FILE_ENUMERATOR,
- "container", file,
- NULL);
- local->dir = dir;
- local->filename = filename;
- local->matcher = g_file_attribute_matcher_new (attributes);
-#ifndef USE_GDIR
- local->reduced_matcher = g_file_attribute_matcher_subtract_attributes (local->matcher,
- G_LOCAL_FILE_INFO_NOSTAT_ATTRIBUTES","
- "standard::type");
-#endif
- local->flags = flags;
-
- return G_FILE_ENUMERATOR (local);
+ return _g_local_file_enumerator_new_with_dir (file, dir, attributes, flags, cancellable, error);
}
#ifndef USE_GDIR
@@ -456,3 +495,175 @@ g_local_file_enumerator_close (GFileEnumerator *enumerator,
return TRUE;
}
+
+static GFileEnumerator *
+g_local_file_enumerator_enumerate_children (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+#ifdef USE_GDIR
+ return G_FILE_ENUMERATOR_CLASS (g_local_file_enumerator_parent_class)->
+ enumerate_children (enumerator, child_name, attributes, flags, cancellable, error);
+#else
+ GLocalFileEnumerator *local = G_LOCAL_FILE_ENUMERATOR (enumerator);
+ GFile *child;
+ DIR *dir;
+ int fd;
+ int childfd;
+ GFileEnumerator *ret;
+
+ fd = dirfd (local->dir);
+ if (fd == -1)
+ goto handle_errno;
+
+ childfd = openat (fd, child_name, O_RDONLY|O_DIRECTORY);
+ if (childfd == -1)
+ goto handle_errno;
+
+ child = g_file_get_child (g_file_enumerator_get_container (enumerator), child_name);
+ dir = fdopendir (childfd);
+ ret = _g_local_file_enumerator_new_with_dir (G_LOCAL_FILE (child), dir, attributes, flags, cancellable, error);
+ g_clear_object (&child);
+
+ return g_steal_pointer (&ret);
+
+handle_errno:
+ {
+ int errsv = errno;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ "Error enumerating child '%s': %s",
+ child_name, g_strerror (errsv));
+
+ return NULL;
+ }
+#endif
+}
+
+#ifndef USE_GDIR
+typedef struct
+{
+ GFile *child;
+ gchar *attributes;
+ GFileQueryInfoFlags flags;
+ gint fd;
+} EnumerateChildren;
+
+static void
+enumerate_children_free (gpointer data)
+{
+ EnumerateChildren *state = data;
+
+ if (state->fd != -1)
+ close (state->fd);
+ g_clear_pointer (&state->attributes, g_free);
+ g_clear_object (&state->child);
+ g_slice_free (EnumerateChildren, state);
+}
+
+static void
+enumerate_children_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ EnumerateChildren *state = task_data;
+ GFileEnumerator *ret;
+ GError *error = NULL;
+ DIR *dir;
+
+ g_assert (G_IS_TASK (task));
+ g_assert (state != NULL);
+ g_assert (state->fd >= 0);
+ g_assert (G_IS_FILE (state->child));
+
+ dir = fdopendir (state->fd);
+ ret = _g_local_file_enumerator_new_with_dir (G_LOCAL_FILE (state->child),
+ dir,
+ state->attributes,
+ state->flags,
+ cancellable,
+ &error);
+
+ if (error != NULL)
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_pointer (task, g_steal_pointer (&ret), g_object_unref);
+}
+#endif
+
+static void
+g_local_file_enumerator_enumerate_children_async (GFileEnumerator *enumerator,
+ const gchar *child_name,
+ const gchar *attributes,
+ GFileQueryInfoFlags flags,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+#ifdef USE_GDIR
+ G_FILE_ENUMERATOR_CLASS (g_local_file_enumerator_parent_class)->
+ enumerate_children_async (enumerator, child_name, attributes, flags, cancellable, callback, user_data);
+#else
+ GLocalFileEnumerator *self = G_LOCAL_FILE_ENUMERATOR (enumerator);
+ EnumerateChildren *state;
+ GTask *task;
+ int errsv;
+ int fd;
+
+ task = g_task_new (enumerator, cancellable, callback, user_data);
+ g_task_set_source_tag (task, g_local_file_enumerator_enumerate_children_async);
+ g_task_set_priority (task, io_priority);
+
+ fd = dirfd (self->dir);
+ if (fd == -1)
+ goto handle_errno;
+
+ fd = dup (fd);
+ if (fd == -1)
+ goto handle_errno;
+
+ state = g_slice_new0 (EnumerateChildren);
+ state->child = g_file_get_child (g_file_enumerator_get_container (enumerator), child_name);
+ state->fd = fd;
+ state->attributes = g_strdup (attributes);
+ state->flags = flags;
+
+ g_task_set_task_data (task, state, enumerate_children_free);
+ g_task_run_in_thread (task, enumerate_children_worker);
+
+ goto cleanup;
+
+handle_errno:
+ errsv = errno;
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ g_io_error_from_errno (errsv),
+ "Error enumerating child '%s': %s",
+ child_name, g_strerror (errsv));
+
+cleanup:
+ g_clear_object (&task);
+#endif
+}
+
+static GFileEnumerator *
+g_local_file_enumerator_enumerate_children_finish (GFileEnumerator *enumerator,
+ GAsyncResult *result,
+ GError **error)
+{
+#ifdef USE_GDIR
+ return G_FILE_ENUMERATOR_CLASS (g_local_file_enumerator_parent_class)->
+ enumerate_children_finish (enumerator, result, error);
+#else
+ g_assert (G_IS_LOCAL_FILE_ENUMERATOR (enumerator));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+#endif
+}
diff --git a/gio/tests/enumerator-children.c b/gio/tests/enumerator-children.c
new file mode 100644
index 000000000..59d3d64ac
--- /dev/null
+++ b/gio/tests/enumerator-children.c
@@ -0,0 +1,205 @@
+#include <errno.h>
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+
+#ifdef G_OS_WIN32
+/* Only test if we have openat(), otherwise no guarantees */
+# define SKIP_TESTS
+#endif
+
+#ifndef SKIP_TESTS
+
+static void
+cleanup_skeleton (const gchar *dir)
+{
+ gchar *command = g_strdup_printf ("rm -rf '%s'", dir);
+ system (command);
+}
+
+static gchar *
+create_skeleton (void)
+{
+ static const gchar *skeleton[] = {
+ "a", "a/b", "a/b/c",
+ };
+ gchar *tmpl = g_strdup ("test-file-enumerator-XXXXXX");
+ guint i;
+
+ if (g_mkdtemp (tmpl) == NULL)
+ g_error ("Failed to create skeleton directory: %s",
+ g_strerror (errno));
+
+ for (i = 0; i < G_N_ELEMENTS (skeleton); i++)
+ {
+ gchar *path = g_build_filename (tmpl, skeleton[i], NULL);
+ if (g_mkdir_with_parents (path, 0750) != 0)
+ g_error ("Failed to create directory %s", path);
+ g_free (path);
+ }
+
+ return g_steal_pointer (&tmpl);
+}
+
+static void
+test_enumerate_children (void)
+{
+ gchar *base = create_skeleton ();
+ GFile *file = g_file_new_for_path (base);
+ GFile *a = g_file_get_child (file, "a");
+ GFile *c = g_file_get_child (file, "c");
+ GFileEnumerator *enum_a = NULL;
+ GFileEnumerator *enum_a_b = NULL;
+ GError *error = NULL;
+ gboolean ret;
+
+ /*
+ * - Create an enumerator for "a"
+ * - Move "a" to "c"
+ * - Make sure "b" enumerator can be created from enum_a
+ */
+
+ enum_a = g_file_enumerate_children (a,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert_true (G_IS_FILE_ENUMERATOR (enum_a));
+
+ /* Create child enumerator, ensure it works */
+ enum_a_b = g_file_enumerator_enumerate_children (enum_a,
+ "b",
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert_true (G_IS_FILE_ENUMERATOR (enum_a_b));
+ g_clear_object (&enum_a_b);
+
+ ret = g_file_move (a, c, G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert_true (ret);
+
+ /* Create child enumerator, ensure it works */
+ enum_a_b = g_file_enumerator_enumerate_children (enum_a,
+ "b",
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert_true (G_IS_FILE_ENUMERATOR (enum_a_b));
+ g_clear_object (&enum_a_b);
+
+ /* New a enumerator should fail */
+ g_clear_object (&enum_a);
+ enum_a = g_file_enumerate_children (a,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
+ g_assert_null (enum_a);
+
+ g_clear_object (&enum_a);
+ g_clear_object (&enum_a_b);
+ g_clear_object (&a);
+ g_clear_object (&c);
+ g_clear_object (&file);
+ cleanup_skeleton (base);
+ g_free (base);
+}
+
+static void
+enumerate_children_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GMainLoop *main_loop = user_data;
+ GFileEnumerator *enumerator = G_FILE_ENUMERATOR (object);
+ GFileEnumerator *child_enumerator;
+ GError *error = NULL;
+
+ child_enumerator = g_file_enumerator_enumerate_children_finish (enumerator, result, &error);
+ g_assert_no_error (error);
+ g_assert_true (G_IS_FILE_ENUMERATOR (child_enumerator));
+
+ g_main_loop_quit (main_loop);
+ g_main_loop_unref (main_loop);
+}
+
+static void
+test_enumerate_children_async (void)
+{
+ GMainLoop *main_loop = g_main_loop_new (NULL, FALSE);
+ gchar *base = create_skeleton ();
+ GFile *file = g_file_new_for_path (base);
+ GFile *a = g_file_get_child (file, "a");
+ GFile *c = g_file_get_child (file, "c");
+ GFileEnumerator *enum_a = NULL;
+ GFileEnumerator *enum_a_b = NULL;
+ GError *error = NULL;
+ gboolean ret;
+
+ /*
+ * - Create an enumerator for "a"
+ * - Move "a" to "c"
+ * - Make sure "b" enumerator can be created from enum_a
+ */
+
+ enum_a = g_file_enumerate_children (a,
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert_true (G_IS_FILE_ENUMERATOR (enum_a));
+
+ /* Create child enumerator, ensure it works */
+ enum_a_b = g_file_enumerator_enumerate_children (enum_a,
+ "b",
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert_true (G_IS_FILE_ENUMERATOR (enum_a_b));
+ g_clear_object (&enum_a_b);
+
+ ret = g_file_move (a, c, G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert_true (ret);
+
+ /* Create child enumerator, ensure it works */
+ g_file_enumerator_enumerate_children_async (enum_a,
+ "b",
+ G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ enumerate_children_cb,
+ g_main_loop_ref (main_loop));
+
+ g_main_loop_run (main_loop);
+
+ g_clear_object (&enum_a);
+ g_clear_object (&enum_a_b);
+ g_clear_object (&a);
+ g_clear_object (&c);
+ g_clear_object (&file);
+ cleanup_skeleton (base);
+ g_free (base);
+
+ g_main_loop_unref (main_loop);
+}
+
+#endif
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+#ifndef SKIP_TESTS
+ g_test_add_func ("/Gio/LocalFileEnumerator/enumerate_children", test_enumerate_children);
+ g_test_add_func ("/Gio/LocalFileEnumerator/enumerate_children_async", test_enumerate_children_async);
+#endif
+
+ return g_test_run ();
+}
diff --git a/gio/tests/meson.build b/gio/tests/meson.build
index a3efd33ab..e59607db2 100644
--- a/gio/tests/meson.build
+++ b/gio/tests/meson.build
@@ -41,6 +41,7 @@ gio_tests = {
'data-input-stream' : {},
'data-output-stream' : {},
'defaultvalue' : {'extra_sources' : [giotypefuncs_inc]},
+ 'enumerator-children' : {},
'fileattributematcher' : {},
'filter-streams' : {},
'giomodule' : {},