[glib/wip/chergert/enumerate-children] gio: add g_file_enumerator_enumerate_children()



commit 455e6221219b214888943bd462a33555bfe138f6
Author: Christian Hergert <chergert redhat com>
Date:   Mon Apr 8 12:06:44 2019 -0700

    gio: add g_file_enumerator_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

 docs/reference/gio/gio-sections.txt |   3 +
 gio/gfileenumerator.c               | 228 ++++++++++++++++++++++++++++++++
 gio/gfileenumerator.h               |  41 +++++-
 gio/glocalfileenumerator.c          | 251 +++++++++++++++++++++++++++++++++---
 gio/tests/enumerator-children.c     | 205 +++++++++++++++++++++++++++++
 gio/tests/meson.build               |   1 +
 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' : {},


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]