fuse-overlayfs: rename uses a .wh. style whiteout

during a rename, use a .wh. style whiteout for the destination, so
that if the rename fails we don't make visible files from the lower
layers.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
Giuseppe Scrivano 2018-07-15 14:45:49 +02:00
parent e908a68156
commit cc18f5ce18
No known key found for this signature in database
GPG Key ID: E4730F97F60286ED

173
main.c
View File

@ -294,13 +294,13 @@ has_prefix (const char *str, const char *pref)
} }
static int static int
create_whiteout (struct ovl_data *lo, struct ovl_node *parent, const char *name) create_whiteout (struct ovl_data *lo, struct ovl_node *parent, const char *name, bool skip_mknod)
{ {
char whiteout_path[PATH_MAX + 10]; char whiteout_path[PATH_MAX + 10];
int fd = -1; int fd = -1;
static bool can_mknod = true; static bool can_mknod = true;
if (can_mknod) if (!skip_mknod && can_mknod)
{ {
int ret; int ret;
@ -338,23 +338,25 @@ delete_whiteout (struct ovl_data *lo, int dirfd, struct ovl_node *parent, const
&& major (st.st_rdev) == 0 && major (st.st_rdev) == 0
&& minor (st.st_rdev) == 0) && minor (st.st_rdev) == 0)
{ {
return unlinkat (dirfd, name, 0); if (unlinkat (dirfd, name, 0) < 0)
return -1;
} }
} }
else else
{ {
sprintf (whiteout_path, "%s/.wh.%s", parent->path, name); sprintf (whiteout_path, "%s/%s", parent->path, name);
if (TEMP_FAILURE_RETRY (fstatat (get_upper_layer (lo)->fd, whiteout_path, &st, AT_SYMLINK_NOFOLLOW)) == 0 if (TEMP_FAILURE_RETRY (fstatat (get_upper_layer (lo)->fd, whiteout_path, &st, AT_SYMLINK_NOFOLLOW)) == 0
&& (st.st_mode & S_IFMT) == S_IFCHR && (st.st_mode & S_IFMT) == S_IFCHR
&& major (st.st_rdev) == 0 && major (st.st_rdev) == 0
&& minor (st.st_rdev) == 0) && minor (st.st_rdev) == 0)
{ {
return unlinkat (get_upper_layer (lo)->fd, whiteout_path, 0); if (unlinkat (get_upper_layer (lo)->fd, whiteout_path, 0) < 0)
return -1;
} }
} }
/* If the whiteout was not found, try the .wh. alternative used when running as non-root. */ /* Look for the .wh. alternative as well. */
if (dirfd >= 0) if (dirfd >= 0)
{ {
@ -389,27 +391,9 @@ hide_node (struct ovl_data *lo, struct ovl_node *node, bool unlink_src)
/* Might be leftover from a previous run. */ /* Might be leftover from a previous run. */
unlinkat (lo->workdir_fd, newpath, 0); unlinkat (lo->workdir_fd, newpath, 0);
unlinkat (lo->workdir_fd, newpath, AT_REMOVEDIR);
if (! unlink_src) if (unlink_src)
{
if (node_dirp (node))
{
if (renameat (node_dirfd (node), node->path, lo->workdir_fd, newpath) < 0)
{
free (newpath);
return -1;
}
}
else
{
if (linkat (node_dirfd (node), node->path, lo->workdir_fd, newpath, 0) < 0)
{
free (newpath);
return -1;
}
}
}
else
{ {
/* If the atomic rename+mknod failed, then fallback into doing it in two steps. */ /* If the atomic rename+mknod failed, then fallback into doing it in two steps. */
if (syscall (SYS_renameat2, node_dirfd (node), node->path, lo->workdir_fd, if (syscall (SYS_renameat2, node_dirfd (node), node->path, lo->workdir_fd,
@ -422,11 +406,30 @@ hide_node (struct ovl_data *lo, struct ovl_node *node, bool unlink_src)
} }
if (node->parent) if (node->parent)
{ {
if (create_whiteout (lo, node->parent, node->name) < 0) if (create_whiteout (lo, node->parent, node->name, false) < 0)
return -1; return -1;
} }
} }
} }
else
{
if (node_dirp (node))
{
if (mkdirat (lo->workdir_fd, newpath, 0700) < 0)
{
free (newpath);
return -1;
}
}
else
{
if (linkat (node_dirfd (node), node->path, lo->workdir_fd, newpath, 0) < 0)
{
free (newpath);
return -1;
}
}
}
node->hidden_dirfd = lo->workdir_fd; node->hidden_dirfd = lo->workdir_fd;
free (node->path); free (node->path);
node->path = newpath; node->path = newpath;
@ -761,7 +764,7 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char
DIR *dp; DIR *dp;
struct dirent *dent; struct dirent *dent;
struct stat st; struct stat st;
struct ovl_layer *it; struct ovl_layer *it, *upper_layer = get_upper_layer (lo);
if (!n) if (!n)
{ {
@ -804,9 +807,19 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char
child = hash_lookup (n->children, &key); child = hash_lookup (n->children, &key);
if (child) if (child)
{ {
if (it->low) if (child->whiteout && it == upper_layer)
child->present_lowerdir = 1; {
continue; hash_delete (n->children, child);
node_free (child);
child = NULL;
}
else
{
if (it->low)
child->present_lowerdir = 1;
child->ino = st.st_ino;
continue;
}
} }
sprintf (node_path, "%s/%s", n->path, dent->d_name); sprintf (node_path, "%s/%s", n->path, dent->d_name);
@ -940,6 +953,7 @@ do_lookup_file (struct ovl_data *lo, fuse_ino_t parent, const char *name)
char path[PATH_MAX]; char path[PATH_MAX];
struct ovl_layer *it; struct ovl_layer *it;
struct stat st; struct stat st;
struct ovl_layer *upper_layer = get_upper_layer (lo);
for (it = lo->layers; it; it = it->next) for (it = lo->layers; it; it = it->next)
{ {
@ -964,10 +978,19 @@ do_lookup_file (struct ovl_data *lo, fuse_ino_t parent, const char *name)
/* If we already know the node, simply update the ino. */ /* If we already know the node, simply update the ino. */
if (node) if (node)
{ {
node->ino = st.st_ino; if (node->whiteout && it == upper_layer)
if (it->low) {
node->present_lowerdir = 1; hash_delete (pnode->children, node);
continue; node_free (node);
node = NULL;
}
else
{
node->ino = st.st_ino;
if (it->low)
node->present_lowerdir = 1;
continue;
}
} }
wh_name = get_whiteout_name (name, &st); wh_name = get_whiteout_name (name, &st);
@ -1185,7 +1208,7 @@ create_missing_whiteouts (struct ovl_data *lo, struct ovl_node *node, const char
continue; continue;
} }
if (create_whiteout (lo, node, dent->d_name) < 0) if (create_whiteout (lo, node, dent->d_name, false) < 0)
{ {
closedir (dp); closedir (dp);
return -1; return -1;
@ -1573,11 +1596,6 @@ copyup (struct ovl_data *lo, struct ovl_node *node)
ret = create_node_directory (lo, node->parent); ret = create_node_directory (lo, node->parent);
if (ret < 0) if (ret < 0)
return ret; return ret;
char whpath[PATH_MAX + 10];
sprintf (whpath, "%s/.wh.%s", node->parent->path, node->name);
if (unlinkat (get_upper_layer (lo)->fd, whpath, 0) < 0 && errno != ENOENT)
goto exit;
} }
if ((st.st_mode & S_IFMT) == S_IFDIR) if ((st.st_mode & S_IFMT) == S_IFDIR)
@ -1654,6 +1672,14 @@ copyup (struct ovl_data *lo, struct ovl_node *node)
if (ret < 0) if (ret < 0)
goto exit; goto exit;
if (node->parent)
{
char whpath[PATH_MAX + 10];
sprintf (whpath, "%s/.wh.%s", node->parent->path, node->name);
if (unlinkat (get_upper_layer (lo)->fd, whpath, 0) < 0 && errno != ENOENT)
goto exit;
}
success: success:
ret = 0; ret = 0;
@ -2705,7 +2731,7 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
struct ovl_node key; struct ovl_node key;
node = do_lookup_file (lo, parent, name); node = do_lookup_file (lo, parent, name);
if (node == NULL) if (node == NULL || node->whiteout)
{ {
fuse_reply_err (req, ENOENT); fuse_reply_err (req, ENOENT);
return; return;
@ -2762,41 +2788,31 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
goto error; goto error;
} }
if (destnode != NULL && !destnode->whiteout && node_dirp (destnode))
{
size_t whiteouts = 0;
destnode = load_dir (lo, destnode, destnode->layer, destnode->path, destnode->name);
if (destnode == NULL)
goto error;
if (count_dir_entries (destnode, &whiteouts) > 0)
{
errno = ENOTEMPTY;
goto error;
}
if (whiteouts && empty_dir (lo, destnode) < 0)
goto error;
if (create_missing_whiteouts (lo, node, destnode->path) < 0)
{
fuse_reply_err (req, errno);
return;
}
}
if (destnode) if (destnode)
{ {
size_t destnode_whiteouts = 0;
if (!destnode->whiteout && destnode->ino == node->ino) if (!destnode->whiteout && destnode->ino == node->ino)
goto error; goto error;
if (node_dirp (node) && destnode->present_lowerdir) if (!destnode->whiteout && node_dirp (destnode))
{ {
if (create_missing_whiteouts (lo, node, destnode->path) < 0) destnode = load_dir (lo, destnode, destnode->layer, destnode->path, destnode->name);
if (destnode == NULL)
goto error;
if (count_dir_entries (destnode, &destnode_whiteouts) > 0)
{
errno = ENOTEMPTY;
goto error;
}
if (destnode_whiteouts && empty_dir (lo, destnode) < 0)
goto error; goto error;
} }
if (node_dirp (node) && create_missing_whiteouts (lo, node, destnode->path) < 0)
goto error;
if (destnode->lookups > 0) if (destnode->lookups > 0)
node_free (destnode); node_free (destnode);
else else
@ -2822,7 +2838,16 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
} }
} }
unlinkat (destfd, newname, 0); /* If the node is a directory we must ensure there is no whiteout at the
destination, otherwise the renameat2 will fail. Create a .wh.$NAME style
whiteout file until the renameat2 is completed. */
if (node_dirp (node))
{
ret = create_whiteout (lo, destpnode, newname, true);
if (ret < 0)
goto error;
unlinkat (destfd, newname, 0);
}
/* Try to create the whiteout atomically, if it fails do the /* Try to create the whiteout atomically, if it fails do the
rename+mknod separately. */ rename+mknod separately. */
@ -2834,11 +2859,14 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
if (ret < 0) if (ret < 0)
goto error; goto error;
ret = create_whiteout (lo, pnode, name); ret = create_whiteout (lo, pnode, name, false);
if (ret < 0) if (ret < 0)
goto error; goto error;
} }
if (delete_whiteout (lo, destfd, NULL, newname) < 0)
goto error;
hash_delete (pnode->children, node); hash_delete (pnode->children, node);
free (node->name); free (node->name);
@ -2855,9 +2883,6 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
if (update_paths (node) < 0) if (update_paths (node) < 0)
goto error; goto error;
if (delete_whiteout (lo, destfd, NULL, newname) < 0)
goto error;
ret = 0; ret = 0;
goto cleanup; goto cleanup;
@ -2964,7 +2989,7 @@ hide_all (struct ovl_data *lo, struct ovl_node *node)
int ret; int ret;
it = nodes[i]; it = nodes[i];
ret = create_whiteout (lo, node, it->name); ret = create_whiteout (lo, node, it->name, false);
node_free (it); node_free (it);
if (ret < 0) if (ret < 0)