From 7b9901e3fc8aa58cf9c0579c29340506269ebfbd Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 9 Sep 2013 16:00:20 -0400 Subject: 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 file 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 +#include #include +#include +#include + +/* 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; } -- cgit v1.2.1