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
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];
int fd = -1;
static bool can_mknod = true;
if (can_mknod)
if (!skip_mknod && can_mknod)
{
int ret;
@ -338,23 +338,25 @@ delete_whiteout (struct ovl_data *lo, int dirfd, struct ovl_node *parent, const
&& major (st.st_rdev) == 0
&& minor (st.st_rdev) == 0)
{
return unlinkat (dirfd, name, 0);
if (unlinkat (dirfd, name, 0) < 0)
return -1;
}
}
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
&& (st.st_mode & S_IFMT) == S_IFCHR
&& major (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)
{
@ -389,27 +391,9 @@ hide_node (struct ovl_data *lo, struct ovl_node *node, bool unlink_src)
/* Might be leftover from a previous run. */
unlinkat (lo->workdir_fd, newpath, 0);
unlinkat (lo->workdir_fd, newpath, AT_REMOVEDIR);
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 (unlink_src)
{
/* 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,
@ -422,11 +406,30 @@ hide_node (struct ovl_data *lo, struct ovl_node *node, bool unlink_src)
}
if (node->parent)
{
if (create_whiteout (lo, node->parent, node->name) < 0)
if (create_whiteout (lo, node->parent, node->name, false) < 0)
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;
free (node->path);
node->path = newpath;
@ -761,7 +764,7 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char
DIR *dp;
struct dirent *dent;
struct stat st;
struct ovl_layer *it;
struct ovl_layer *it, *upper_layer = get_upper_layer (lo);
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);
if (child)
{
if (it->low)
child->present_lowerdir = 1;
continue;
if (child->whiteout && it == upper_layer)
{
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);
@ -940,6 +953,7 @@ do_lookup_file (struct ovl_data *lo, fuse_ino_t parent, const char *name)
char path[PATH_MAX];
struct ovl_layer *it;
struct stat st;
struct ovl_layer *upper_layer = get_upper_layer (lo);
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 (node)
{
node->ino = st.st_ino;
if (it->low)
node->present_lowerdir = 1;
continue;
if (node->whiteout && it == upper_layer)
{
hash_delete (pnode->children, node);
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);
@ -1185,7 +1208,7 @@ create_missing_whiteouts (struct ovl_data *lo, struct ovl_node *node, const char
continue;
}
if (create_whiteout (lo, node, dent->d_name) < 0)
if (create_whiteout (lo, node, dent->d_name, false) < 0)
{
closedir (dp);
return -1;
@ -1573,11 +1596,6 @@ copyup (struct ovl_data *lo, struct ovl_node *node)
ret = create_node_directory (lo, node->parent);
if (ret < 0)
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)
@ -1654,6 +1672,14 @@ copyup (struct ovl_data *lo, struct ovl_node *node)
if (ret < 0)
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:
ret = 0;
@ -2705,7 +2731,7 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
struct ovl_node key;
node = do_lookup_file (lo, parent, name);
if (node == NULL)
if (node == NULL || node->whiteout)
{
fuse_reply_err (req, ENOENT);
return;
@ -2762,41 +2788,31 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
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)
{
size_t destnode_whiteouts = 0;
if (!destnode->whiteout && destnode->ino == node->ino)
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;
}
if (node_dirp (node) && create_missing_whiteouts (lo, node, destnode->path) < 0)
goto error;
if (destnode->lookups > 0)
node_free (destnode);
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
rename+mknod separately. */
@ -2834,11 +2859,14 @@ ovl_rename_direct (fuse_req_t req, fuse_ino_t parent, const char *name,
if (ret < 0)
goto error;
ret = create_whiteout (lo, pnode, name);
ret = create_whiteout (lo, pnode, name, false);
if (ret < 0)
goto error;
}
if (delete_whiteout (lo, destfd, NULL, newname) < 0)
goto error;
hash_delete (pnode->children, node);
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)
goto error;
if (delete_whiteout (lo, destfd, NULL, newname) < 0)
goto error;
ret = 0;
goto cleanup;
@ -2964,7 +2989,7 @@ hide_all (struct ovl_data *lo, struct ovl_node *node)
int ret;
it = nodes[i];
ret = create_whiteout (lo, node, it->name);
ret = create_whiteout (lo, node, it->name, false);
node_free (it);
if (ret < 0)