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 <gscrivan@redhat.com>
This commit is contained in:
Giuseppe Scrivano 2020-06-17 15:00:07 +02:00
parent f3e4154078
commit f26f1f71ae
No known key found for this signature in database
GPG Key ID: E4730F97F60286ED

150
main.c
View File

@ -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;