Skip to content

Commit

Permalink
ovl: proper cleanup of workdir
Browse files Browse the repository at this point in the history
When mounting overlayfs it needs a clean "work" directory under the
supplied workdir.

Previously the mount code removed this directory if it already existed and
created a new one.  If the removal failed (e.g. directory was not empty)
then it fell back to a read-only mount not using the workdir.

While this has never been reported, it is possible to get a non-empty
"work" dir from a previous mount of overlayfs in case of crash in the
middle of an operation using the work directory.

In this case the left over state should be discarded and the overlay
filesystem will be consistent, guaranteed by the atomicity of operations on
moving to/from the workdir to the upper layer.

This patch implements cleaning out any files left in workdir.  It is
implemented using real recursion for simplicity, but the depth is limited
to 2, because the worst case is that of a directory containing whiteouts
under "work".

Signed-off-by: Miklos Szeredi <[email protected]>
Cc: <[email protected]>
  • Loading branch information
Miklos Szeredi committed Sep 1, 2016
1 parent c11b9fd commit eea2fb4
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 2 deletions.
2 changes: 2 additions & 0 deletions fs/overlayfs/overlayfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list);
void ovl_cleanup_whiteouts(struct dentry *upper, struct list_head *list);
void ovl_cache_free(struct list_head *list);
int ovl_check_d_type_supported(struct path *realpath);
void ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt,
struct dentry *dentry, int level);

/* inode.c */
int ovl_setattr(struct dentry *dentry, struct iattr *attr);
Expand Down
63 changes: 62 additions & 1 deletion fs/overlayfs/readdir.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ static inline int ovl_dir_read(struct path *realpath,
err = rdd->err;
} while (!err && rdd->count);

if (!err && rdd->first_maybe_whiteout)
if (!err && rdd->first_maybe_whiteout && rdd->dentry)
err = ovl_check_whiteouts(realpath->dentry, rdd);

fput(realfile);
Expand Down Expand Up @@ -606,3 +606,64 @@ int ovl_check_d_type_supported(struct path *realpath)

return rdd.d_type_supported;
}

static void ovl_workdir_cleanup_recurse(struct path *path, int level)
{
int err;
struct inode *dir = path->dentry->d_inode;
LIST_HEAD(list);
struct ovl_cache_entry *p;
struct ovl_readdir_data rdd = {
.ctx.actor = ovl_fill_merge,
.dentry = NULL,
.list = &list,
.root = RB_ROOT,
.is_lowest = false,
};

err = ovl_dir_read(path, &rdd);
if (err)
goto out;

inode_lock_nested(dir, I_MUTEX_PARENT);
list_for_each_entry(p, &list, l_node) {
struct dentry *dentry;

if (p->name[0] == '.') {
if (p->len == 1)
continue;
if (p->len == 2 && p->name[1] == '.')
continue;
}
dentry = lookup_one_len(p->name, path->dentry, p->len);
if (IS_ERR(dentry))
continue;
if (dentry->d_inode)
ovl_workdir_cleanup(dir, path->mnt, dentry, level);
dput(dentry);
}
inode_unlock(dir);
out:
ovl_cache_free(&list);
}

void ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt,
struct dentry *dentry, int level)
{
int err;

if (!d_is_dir(dentry) || level > 1) {
ovl_cleanup(dir, dentry);
return;
}

err = ovl_do_rmdir(dir, dentry);
if (err) {
struct path path = { .mnt = mnt, .dentry = dentry };

inode_unlock(dir);
ovl_workdir_cleanup_recurse(&path, level + 1);
inode_lock_nested(dir, I_MUTEX_PARENT);
ovl_cleanup(dir, dentry);
}
}
2 changes: 1 addition & 1 deletion fs/overlayfs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@ static struct dentry *ovl_workdir_create(struct vfsmount *mnt,
goto out_dput;

retried = true;
ovl_cleanup(dir, work);
ovl_workdir_cleanup(dir, mnt, work, 0);
dput(work);
goto retry;
}
Expand Down

0 comments on commit eea2fb4

Please sign in to comment.