[libgsystem] shutil: Use at-relative walking for gs_shutil_rm_rf()



commit 7b9901e3fc8aa58cf9c0579c29340506269ebfbd
Author: Colin Walters <walters verbum org>
Date:   Mon Sep 9 16:00:20 2013 -0400

    shutil: Use at-relative walking for gs_shutil_rm_rf()
    
    This is safer against concurrent modification, as well as being more
    efficient.

 gsystem-shutil.c |  229 +++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 167 insertions(+), 62 deletions(-)
---
diff --git a/gsystem-shutil.c b/gsystem-shutil.c
index 7fe9be7..4ea3ca9 100644
--- a/gsystem-shutil.c
+++ b/gsystem-shutil.c
@@ -28,7 +28,17 @@
 #define _GSYSTEM_NO_LOCAL_ALLOC
 #include "libgsystem.h"
 #include <glib-unix.h>
+#include <string.h>
 #include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+
+/* Taken from systemd/src/shared/util.h */
+union dirent_storage {
+        struct dirent dent;
+        guint8 storage[offsetof(struct dirent, d_name) +
+                        ((NAME_MAX + 1 + sizeof(long)) & ~(sizeof(long) - 1))];
+};
 
 static gboolean
 cp_internal (GFile         *src,
@@ -195,6 +205,134 @@ gs_shutil_cp_a (GFile         *src,
   return cp_internal (src, dest, FALSE, cancellable, error);
 }
 
+static unsigned char
+struct_stat_to_dt (struct stat *stbuf)
+{
+  if (S_ISDIR (stbuf->st_mode))
+    return DT_DIR;
+  if (S_ISREG (stbuf->st_mode))
+    return DT_REG;
+  if (S_ISCHR (stbuf->st_mode))
+    return DT_CHR;
+  if (S_ISBLK (stbuf->st_mode))
+    return DT_BLK;
+  if (S_ISFIFO (stbuf->st_mode))
+    return DT_FIFO;
+  if (S_ISLNK (stbuf->st_mode))
+    return DT_LNK;
+  if (S_ISSOCK (stbuf->st_mode))
+    return DT_SOCK;
+  return DT_UNKNOWN;
+}
+
+static gboolean
+gs_shutil_rm_rf_children (DIR                *dir,
+                          GCancellable       *cancellable,
+                          GError            **error)
+{
+  gboolean ret = FALSE;
+  int dfd;
+  DIR *child_dir = NULL;
+  struct dirent *dent;
+  union dirent_storage buf;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    goto out;
+
+  dfd = dirfd (dir);
+
+  while (readdir_r (dir, &buf.dent, &dent) == 0)
+    {
+      if (dent == NULL)
+        break;
+      if (dent->d_type == DT_UNKNOWN)
+        {
+          struct stat stbuf;
+          if (fstatat (dfd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
+            {
+              int errsv = errno;
+              if (errsv == ENOENT)
+                continue;
+              else
+                {
+                  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                       g_strerror (errsv));
+                  goto out;
+                }
+            }
+          dent->d_type = struct_stat_to_dt (&stbuf);
+          /* Assume unknown types are just treated like regular files */
+          if (dent->d_type == DT_UNKNOWN)
+            dent->d_type = DT_REG;
+        }
+
+      if (strcmp (dent->d_name, ".") == 0 || strcmp (dent->d_name, "..") == 0)
+        continue;
+          
+      if (dent->d_type == DT_DIR)
+        {
+          int child_dfd = openat (dfd, dent->d_name, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | 
O_NOFOLLOW);
+
+          if (child_dfd == -1)
+            {
+              if (errno == ENOENT)
+                continue;
+              else
+                {
+                  int errsv = errno;
+                  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                       g_strerror (errsv));
+                  goto out;
+                }
+            }
+
+          child_dir = fdopendir (child_dfd);
+          if (!child_dir)
+            {
+              int errsv = errno;
+              g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                   g_strerror (errsv));
+              goto out;
+            }
+
+          if (!gs_shutil_rm_rf_children (child_dir, cancellable, error))
+            goto out;
+
+          if (unlinkat (dfd, dent->d_name, AT_REMOVEDIR) == -1)
+            {
+              int errsv = errno;
+              g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                   g_strerror (errsv));
+              goto out;
+            }
+
+          (void) closedir (child_dir);
+          child_dir = NULL;
+        }
+      else
+        {
+          if (unlinkat (dfd, dent->d_name, 0) == -1)
+            {
+              int errsv = errno;
+              if (errno != ENOENT)
+                {
+                  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                       g_strerror (errsv));
+                  goto out;
+                }
+            }
+        }
+    }
+  /* Ignore error result from readdir_r, that's what others
+   * seem to do =(
+   */
+
+  ret = TRUE;
+ out:
+  if (child_dir) (void) closedir (child_dir);
+  return ret;
+}
+
 /**
  * gs_shutil_rm_rf:
  * @path: A file or directory
@@ -210,94 +348,61 @@ gs_shutil_rm_rf (GFile        *path,
                  GError      **error)
 {
   gboolean ret = FALSE;
-  GFileEnumerator *dir_enum = NULL;
-  GFileInfo *file_info = NULL;
-  GError *temp_error = NULL;
+  int dfd = -1;
+  DIR *d = NULL;
 
-  if (!gs_file_unlink (path, cancellable, &temp_error))
+  /* With O_NOFOLLOW first */
+  dfd = openat (AT_FDCWD, gs_file_get_path_cached (path),
+                O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
+
+  if (dfd == -1)
     {
-      if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+      int errsv = errno;
+      if (errsv == ENOENT)
         {
-          g_clear_error (&temp_error);
-          ret = TRUE;
-          goto out;
+          ;
         }
-      else if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY))
+      else if (errsv == ENOTDIR)
         {
-          g_clear_error (&temp_error);
-          /* Fall through */
+          if (!gs_file_unlink (path, cancellable, error))
+            goto out;
         }
       else
         {
-          g_propagate_error (error, temp_error);
+          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                               g_strerror (errsv));
           goto out;
         }
     }
   else
     {
-      ret = TRUE;
-      goto out;
-    }
-
-  dir_enum = g_file_enumerate_children (path, "standard::type,standard::name", 
-                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                        cancellable, &temp_error);
-  if (!dir_enum)
-    {
-      if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
-        {
-          g_clear_error (&temp_error);
-          ret = TRUE;
-        }
-      else
+      d = fdopendir (dfd);
+      if (!d)
         {
-          g_propagate_error (error, temp_error);
+          int errsv = errno;
+          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                               g_strerror (errsv));
+          goto out;
         }
-      goto out;
-    }
 
-  while ((file_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
-    {
-      GFile *subpath = NULL;
-      GFileType type;
-      const char *name;
-
-      type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
-      name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
-      
-      subpath = g_file_get_child (path, name);
+      if (!gs_shutil_rm_rf_children (d, cancellable, error))
+        goto out;
 
-      if (type == G_FILE_TYPE_DIRECTORY)
+      if (rmdir (gs_file_get_path_cached (path)) == -1)
         {
-          if (!gs_shutil_rm_rf (subpath, cancellable, error))
-            {
-              g_object_unref (subpath);
-              goto out;
-            }
-        }
-      else
-        {
-          if (!gs_file_unlink (subpath, cancellable, error))
+          int errsv = errno;
+          if (errsv != ENOENT)
             {
-              g_object_unref (subpath);
+              g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                   g_strerror (errsv));
               goto out;
             }
         }
-      g_clear_object (&file_info);
-    }
-  if (temp_error)
-    {
-      g_propagate_error (error, temp_error);
-      goto out;
     }
 
-  if (!g_file_delete (path, cancellable, error))
-    goto out;
-
   ret = TRUE;
  out:
-  g_clear_object (&dir_enum);
-  g_clear_object (&file_info);
+  if (d) (void) closedir (d);
   return ret;
 }
 


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