fuse-overlayfs: allow copy of a dir over an existing in the lower layers

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
Giuseppe Scrivano 2018-07-12 21:50:12 +02:00
parent 86f98b4c91
commit b054b9a191
No known key found for this signature in database
GPG Key ID: E4730F97F60286ED

169
main.c
View File

@ -252,6 +252,12 @@ get_upper_layer (struct ovl_data *lo)
return lo->layers;
}
static struct ovl_layer *
get_lower_layers (struct ovl_data *lo)
{
return lo->layers->next;
}
static inline bool
node_dirp (struct ovl_node *n)
{
@ -382,10 +388,21 @@ hide_node (struct ovl_data *lo, struct ovl_node *node, bool unlink_src)
if (! unlink_src)
{
if (linkat (node_dirfd (node), node->path, lo->workdir_fd, newpath, 0) < 0)
if (node_dirp (node))
{
free (newpath);
return -1;
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
@ -574,7 +591,7 @@ node_compare (const void *n1, const void *n2)
}
static struct ovl_node *
make_whiteout_node (const char *name)
make_whiteout_node (const char *path, const char *name)
{
struct ovl_node *ret = calloc (1, sizeof (*ret));
if (ret == NULL)
@ -589,6 +606,14 @@ make_whiteout_node (const char *name)
errno = ENOMEM;
return NULL;
}
ret->path = strdup (path);
if (ret->path == NULL)
{
free (ret->name);
free (ret);
errno = ENOMEM;
return NULL;
}
ret->whiteout = 1;
return ret;
}
@ -759,6 +784,7 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char
struct ovl_node key;
const char *wh;
struct ovl_node *child = NULL;
char node_path[PATH_MAX + 1];
key.name = dent->d_name;
@ -779,10 +805,12 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char
continue;
}
sprintf (node_path, "%s/%s", n->path, dent->d_name);
wh = get_whiteout_name (dent->d_name, &st);
if (wh)
{
child = make_whiteout_node (wh);
child = make_whiteout_node (node_path, wh);
if (child == NULL)
{
errno = ENOMEM;
@ -793,9 +821,7 @@ load_dir (struct ovl_data *lo, struct ovl_node *n, struct ovl_layer *layer, char
else
{
bool dirp = st.st_mode & S_IFDIR;
char node_path[PATH_MAX + 1];
sprintf (node_path, "%s/%s", n->path, dent->d_name);
child = make_ovl_node (node_path, it, dent->d_name, 0, dirp);
if (child == NULL)
@ -942,7 +968,7 @@ do_lookup_file (struct ovl_data *lo, fuse_ino_t parent, const char *name)
wh_name = get_whiteout_name (name, &st);
if (wh_name)
node = make_whiteout_node (wh_name);
node = make_whiteout_node (path, wh_name);
else
node = make_ovl_node (path, it, name, 0, st.st_mode & S_IFDIR);
if (node == NULL)
@ -1081,6 +1107,68 @@ out_errno:
fuse_reply_err (req, errno);
}
static int
create_missing_whiteouts (struct ovl_data *lo, struct ovl_node *node, struct ovl_node *from)
{
struct ovl_layer *l;
if (! node_dirp (node))
return 0;
node = load_dir (lo, node, node->layer, node->path, node->name);
if (node == NULL)
return -1;
for (l = get_lower_layers (lo); l; l = l->next)
{
DIR *dp;
int fd;
fd = TEMP_FAILURE_RETRY (openat (l->fd, from->path, O_DIRECTORY));
if (fd < 0)
{
if (errno == ENOENT)
continue;
if (errno == ENOTDIR)
break;
return -1;
}
dp = fdopendir (fd);
if (dp)
{
struct dirent *dent;
while (dp && ((dent = readdir (dp)) != NULL))
{
struct ovl_node key;
struct ovl_node *n;
if (strcmp (dent->d_name, ".") == 0)
continue;
if (strcmp (dent->d_name, "..") == 0)
continue;
key.name = (char *) dent->d_name;
n = hash_lookup (node->children, &key);
if (n)
continue;
if (create_whiteout (lo, node, dent->d_name) < 0)
{
closedir (dp);
return -1;
}
}
closedir (dp);
}
}
return 0;
}
static void
ovl_do_readdir (fuse_req_t req, fuse_ino_t ino, size_t size,
off_t offset, struct fuse_file_info *fi, int plus)
@ -1574,15 +1662,22 @@ get_node_up (struct ovl_data *lo, struct ovl_node *node)
}
static size_t
count_dir_entries (struct ovl_node *node)
count_dir_entries (struct ovl_node *node, size_t *whiteouts)
{
size_t c = 0;
struct ovl_node *it;
if (whiteouts)
*whiteouts = 0;
for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
{
if (it->whiteout)
continue;
{
if (whiteouts)
(*whiteouts)++;
continue;
}
if (strcmp (it->name, ".") == 0)
continue;
if (strcmp (it->name, "..") == 0)
@ -1650,7 +1745,7 @@ do_rm (fuse_req_t req, fuse_ino_t parent, const char *name, bool dirp)
return;
}
c = count_dir_entries (node);
c = count_dir_entries (node, NULL);
if (c)
{
fuse_reply_err (req, ENOTEMPTY);
@ -2492,24 +2587,44 @@ ovl_rename (fuse_req_t req, fuse_ino_t parent, const char *name,
}
}
if (destnode != NULL && !destnode->whiteout && node_dirp (destnode))
{
errno = EISDIR;
goto error;
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;
}
/* We support only the case where the destination is completely empty. */
if (whiteouts)
{
errno = EXDEV;
goto error;
}
if (create_missing_whiteouts (lo, node, destnode) < 0)
{
fuse_reply_err (req, errno);
return;
}
}
rm = hash_lookup (destpnode->children, &key);
if (rm)
{
if (!rm->whiteout && rm->ino == node->ino)
goto error;
if (node_dirp (node) && rm->present_lowerdir)
{
fuse_reply_err (req, EXDEV);
return;
}
if (!rm->whiteout && rm->ino == node->ino)
{
fuse_reply_err (req, 0);
return;
if (create_missing_whiteouts (lo, node, rm) < 0)
goto error;
}
hash_delete (destpnode->children, rm);
@ -2590,7 +2705,8 @@ ovl_rename (fuse_req_t req, fuse_ino_t parent, const char *name,
if (ret < 0)
goto error;
if (create_whiteout (lo, pnode, name) < 0)
ret = create_whiteout (lo, pnode, name);
if (ret < 0)
goto error;
}
@ -2599,7 +2715,10 @@ ovl_rename (fuse_req_t req, fuse_ino_t parent, const char *name,
free (node->name);
node->name = strdup (newname);
if (node->name == NULL)
goto error;
{
ret = -1;
goto error;
}
node = insert_node (destpnode, node, true);
if (node == NULL)
@ -2611,7 +2730,13 @@ ovl_rename (fuse_req_t req, fuse_ino_t parent, const char *name,
if (delete_whiteout (lo, destfd, NULL, newname) < 0)
goto error;
ret = 0;
goto cleanup;
error:
ret = -1;
cleanup:
saved_errno = errno;
if (srcfd >= 0)
close (srcfd);