summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Walters <walters@verbum.org>2013-09-09 16:00:20 -0400
committerColin Walters <walters@verbum.org>2013-09-09 16:00:20 -0400
commit7b9901e3fc8aa58cf9c0579c29340506269ebfbd (patch)
tree905c578a8c3057af2915c685e5f06239998db4aa
parentbd2c1e436b270b39ca262765e775b4556d6bd50b (diff)
downloadlibgsystem-7b9901e3fc8aa58cf9c0579c29340506269ebfbd.tar.gz
shutil: Use at-relative walking for gs_shutil_rm_rf()
This is safer against concurrent modification, as well as being more efficient.
-rw-r--r--gsystem-shutil.c229
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;
}