From f26f1f71aec208466f55ba6a2e7d20e4455a0bd0 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 17 Jun 2020 15:00:07 +0200 Subject: [PATCH] mkdir: if the destination exists atomically swap them if the destination already exists as it could not be properly cleaned up, attempt to atomically swap the two directories and free the old one. Closes: https://github.com/containers/fuse-overlayfs/issues/213 Signed-off-by: Giuseppe Scrivano --- main.c | 150 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 84 insertions(+), 66 deletions(-) diff --git a/main.c b/main.c index 15eb416..a59d4e2 100644 --- a/main.c +++ b/main.c @@ -823,7 +823,7 @@ drop_node_from_ino (Hash_table *inodes, struct ovl_node *node) } static int -direct_renameat2 (struct ovl_layer *l, int olddirfd, const char *oldpath, +direct_renameat2 (int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags) { return syscall (SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, flags); @@ -2393,6 +2393,67 @@ copy_xattr (int sfd, int dfd, char *buf, size_t buf_size) return 0; } +static int +empty_dirfd (int fd) +{ + cleanup_dir DIR *dp = NULL; + struct dirent *dent; + + dp = fdopendir (fd); + if (dp == NULL) + { + close (fd); + return -1; + } + + for (;;) + { + int ret; + + errno = 0; + dent = readdir (dp); + if (dent == NULL) + { + if (errno) + return -1; + + break; + } + if (strcmp (dent->d_name, ".") == 0) + continue; + if (strcmp (dent->d_name, "..") == 0) + continue; + + ret = unlinkat (dirfd (dp), dent->d_name, 0); + if (ret < 0 && errno == EISDIR) + { + ret = unlinkat (dirfd (dp), dent->d_name, AT_REMOVEDIR); + if (ret < 0 && errno == ENOTEMPTY) + { + int dfd; + + dfd = safe_openat (dirfd (dp), dent->d_name, O_DIRECTORY, 0); + if (dfd < 0) + return -1; + + ret = empty_dirfd (dfd); + if (ret < 0) + return -1; + + ret = unlinkat (dirfd (dp), dent->d_name, AT_REMOVEDIR); + if (ret < 0) + return -1; + + continue; + } + } + if (ret < 0) + return ret; + } + + return 0; +} + static int create_node_directory (struct ovl_data *lo, struct ovl_node *src); static int @@ -2482,6 +2543,24 @@ create_directory (struct ovl_data *lo, int dirfd, const char *name, const struct ret = renameat (lo->workdir_fd, wd_tmp_file_name, dirfd, name); if (ret < 0) { + if (errno == EEXIST) + { + int dfd = -1; + + ret = direct_renameat2 (lo->workdir_fd, wd_tmp_file_name, dirfd, name, RENAME_EXCHANGE); + if (ret < 0) + goto out; + + dfd = TEMP_FAILURE_RETRY (safe_openat (lo->workdir_fd, wd_tmp_file_name, O_DIRECTORY, 0)); + if (dfd < 0) + return -1; + + ret = empty_dirfd (dfd); + if (ret < 0) + goto out; + + return unlinkat (lo->workdir_fd, wd_tmp_file_name, AT_REMOVEDIR); + } if (errno == ENOTDIR) unlinkat (dirfd, name, 0); if (errno == ENOENT && parent) @@ -2816,67 +2895,6 @@ update_paths (struct ovl_node *node) return 0; } -static int -empty_dirfd (int fd) -{ - cleanup_dir DIR *dp = NULL; - struct dirent *dent; - - dp = fdopendir (fd); - if (dp == NULL) - { - close (fd); - return -1; - } - - for (;;) - { - int ret; - - errno = 0; - dent = readdir (dp); - if (dent == NULL) - { - if (errno) - return -1; - - break; - } - if (strcmp (dent->d_name, ".") == 0) - continue; - if (strcmp (dent->d_name, "..") == 0) - continue; - - ret = unlinkat (dirfd (dp), dent->d_name, 0); - if (ret < 0 && errno == EISDIR) - { - ret = unlinkat (dirfd (dp), dent->d_name, AT_REMOVEDIR); - if (ret < 0 && errno == ENOTEMPTY) - { - int dfd; - - dfd = safe_openat (dirfd (dp), dent->d_name, O_DIRECTORY, 0); - if (dfd < 0) - return -1; - - ret = empty_dirfd (dfd); - if (ret < 0) - return -1; - - ret = unlinkat (dirfd (dp), dent->d_name, AT_REMOVEDIR); - if (ret < 0) - return -1; - - continue; - } - } - if (ret < 0) - return ret; - } - - return 0; -} - static int empty_dir (struct ovl_layer *l, const char *path) { @@ -4006,7 +4024,7 @@ ovl_rename_exchange (fuse_req_t req, fuse_ino_t parent, const char *name, goto error; - ret = direct_renameat2 (node->layer, srcfd, name, destfd, newname, flags); + ret = direct_renameat2 (srcfd, name, destfd, newname, flags); if (ret < 0) goto error; @@ -4179,7 +4197,7 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name, so that with one operation we get both the rename and the whiteout created. */ if (destnode_is_whiteout) { - ret = direct_renameat2 (get_upper_layer (lo), srcfd, name, destfd, newname, flags|RENAME_EXCHANGE); + ret = direct_renameat2 (srcfd, name, destfd, newname, flags|RENAME_EXCHANGE); if (ret == 0) goto done; @@ -4199,11 +4217,11 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name, /* Try to create the whiteout atomically, if it fails do the rename+mknod separately. */ - ret = direct_renameat2 (get_upper_layer (lo), srcfd, name, destfd, + ret = direct_renameat2 (srcfd, name, destfd, newname, flags|RENAME_WHITEOUT); if (ret < 0) { - ret = direct_renameat2 (get_upper_layer (lo), srcfd, name, destfd, + ret = direct_renameat2 (srcfd, name, destfd, newname, flags); if (ret < 0) goto error;