Commit 235ce9ed authored by Amir Goldstein's avatar Amir Goldstein Committed by Miklos Szeredi

ovl: check for incompatible features in work dir

An incompatible feature is marked by a non-empty directory nested
2 levels deep under "work" dir, e.g.:
workdir/work/incompat/volatile.

This commit checks for marked incompat features, warns about them
and fails to mount the overlay, for example:
  overlayfs: overlay with incompat feature 'volatile' cannot be mounted

Very old kernels (i.e. v3.18) will fail to remove a non-empty "work"
dir and fail the mount.  Newer kernels will fail to remove a "work"
dir with entries nested 3 levels and fall back to read-only mount.

User mounting with old kernel will see a warning like these in dmesg:
  overlayfs: cleanup of 'incompat/...' failed (-39)
  overlayfs: cleanup of 'work/incompat' failed (-39)
  overlayfs: cleanup of 'ovl-work/work' failed (-39)
  overlayfs: failed to create directory /vdf/ovl-work/work (errno: 17);
             mounting read-only

These warnings should give the hint to the user that:
1. mount failure is caused by backward incompatible features
2. mount failure can be resolved by manually removing the "work" directory

There is nothing preventing users on old kernels from manually removing
workdir entirely or mounting overlay with a new workdir, so this is in
no way a full proof backward compatibility enforcement, but only a best
effort.
Signed-off-by: default avatarAmir Goldstein <amir73il@gmail.com>
Signed-off-by: default avatarMiklos Szeredi <mszeredi@redhat.com>
parent f75aef39
...@@ -1051,7 +1051,9 @@ int ovl_check_d_type_supported(struct path *realpath) ...@@ -1051,7 +1051,9 @@ int ovl_check_d_type_supported(struct path *realpath)
return rdd.d_type_supported; return rdd.d_type_supported;
} }
static void ovl_workdir_cleanup_recurse(struct path *path, int level) #define OVL_INCOMPATDIR_NAME "incompat"
static int ovl_workdir_cleanup_recurse(struct path *path, int level)
{ {
int err; int err;
struct inode *dir = path->dentry->d_inode; struct inode *dir = path->dentry->d_inode;
...@@ -1065,6 +1067,19 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level) ...@@ -1065,6 +1067,19 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level)
.root = &root, .root = &root,
.is_lowest = false, .is_lowest = false,
}; };
bool incompat = false;
/*
* The "work/incompat" directory is treated specially - if it is not
* empty, instead of printing a generic error and mounting read-only,
* we will error about incompat features and fail the mount.
*
* When called from ovl_indexdir_cleanup(), path->dentry->d_name.name
* starts with '#'.
*/
if (level == 2 &&
!strcmp(path->dentry->d_name.name, OVL_INCOMPATDIR_NAME))
incompat = true;
err = ovl_dir_read(path, &rdd); err = ovl_dir_read(path, &rdd);
if (err) if (err)
...@@ -1079,17 +1094,25 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level) ...@@ -1079,17 +1094,25 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level)
continue; continue;
if (p->len == 2 && p->name[1] == '.') if (p->len == 2 && p->name[1] == '.')
continue; continue;
} else if (incompat) {
pr_err("overlay with incompat feature '%s' cannot be mounted\n",
p->name);
err = -EINVAL;
break;
} }
dentry = lookup_one_len(p->name, path->dentry, p->len); dentry = lookup_one_len(p->name, path->dentry, p->len);
if (IS_ERR(dentry)) if (IS_ERR(dentry))
continue; continue;
if (dentry->d_inode) if (dentry->d_inode)
ovl_workdir_cleanup(dir, path->mnt, dentry, level); err = ovl_workdir_cleanup(dir, path->mnt, dentry, level);
dput(dentry); dput(dentry);
if (err)
break;
} }
inode_unlock(dir); inode_unlock(dir);
out: out:
ovl_cache_free(&list); ovl_cache_free(&list);
return err;
} }
int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt, int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt,
...@@ -1106,9 +1129,10 @@ int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt, ...@@ -1106,9 +1129,10 @@ int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt,
struct path path = { .mnt = mnt, .dentry = dentry }; struct path path = { .mnt = mnt, .dentry = dentry };
inode_unlock(dir); inode_unlock(dir);
ovl_workdir_cleanup_recurse(&path, level + 1); err = ovl_workdir_cleanup_recurse(&path, level + 1);
inode_lock_nested(dir, I_MUTEX_PARENT); inode_lock_nested(dir, I_MUTEX_PARENT);
err = ovl_cleanup(dir, dentry); if (!err)
err = ovl_cleanup(dir, dentry);
} }
return err; return err;
......
...@@ -705,8 +705,12 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs, ...@@ -705,8 +705,12 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
goto out_unlock; goto out_unlock;
retried = true; retried = true;
ovl_workdir_cleanup(dir, mnt, work, 0); err = ovl_workdir_cleanup(dir, mnt, work, 0);
dput(work); dput(work);
if (err == -EINVAL) {
work = ERR_PTR(err);
goto out_unlock;
}
goto retry; goto retry;
} }
...@@ -1203,7 +1207,7 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs, ...@@ -1203,7 +1207,7 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
struct path *workpath) struct path *workpath)
{ {
struct vfsmount *mnt = ovl_upper_mnt(ofs); struct vfsmount *mnt = ovl_upper_mnt(ofs);
struct dentry *temp; struct dentry *temp, *workdir;
bool rename_whiteout; bool rename_whiteout;
bool d_type; bool d_type;
int fh_type; int fh_type;
...@@ -1213,10 +1217,13 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs, ...@@ -1213,10 +1217,13 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
if (err) if (err)
return err; return err;
ofs->workdir = ovl_workdir_create(ofs, OVL_WORKDIR_NAME, false); workdir = ovl_workdir_create(ofs, OVL_WORKDIR_NAME, false);
if (!ofs->workdir) err = PTR_ERR(workdir);
if (IS_ERR_OR_NULL(workdir))
goto out; goto out;
ofs->workdir = workdir;
err = ovl_setup_trap(sb, ofs->workdir, &ofs->workdir_trap, "workdir"); err = ovl_setup_trap(sb, ofs->workdir, &ofs->workdir_trap, "workdir");
if (err) if (err)
goto out; goto out;
...@@ -1347,6 +1354,7 @@ static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs, ...@@ -1347,6 +1354,7 @@ static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs,
struct ovl_entry *oe, struct path *upperpath) struct ovl_entry *oe, struct path *upperpath)
{ {
struct vfsmount *mnt = ovl_upper_mnt(ofs); struct vfsmount *mnt = ovl_upper_mnt(ofs);
struct dentry *indexdir;
int err; int err;
err = mnt_want_write(mnt); err = mnt_want_write(mnt);
...@@ -1366,9 +1374,12 @@ static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs, ...@@ -1366,9 +1374,12 @@ static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs,
ofs->workdir_trap = NULL; ofs->workdir_trap = NULL;
dput(ofs->workdir); dput(ofs->workdir);
ofs->workdir = NULL; ofs->workdir = NULL;
ofs->indexdir = ovl_workdir_create(ofs, OVL_INDEXDIR_NAME, true); indexdir = ovl_workdir_create(ofs, OVL_INDEXDIR_NAME, true);
if (ofs->indexdir) { if (IS_ERR(indexdir)) {
ofs->workdir = dget(ofs->indexdir); err = PTR_ERR(indexdir);
} else if (indexdir) {
ofs->indexdir = indexdir;
ofs->workdir = dget(indexdir);
err = ovl_setup_trap(sb, ofs->indexdir, &ofs->indexdir_trap, err = ovl_setup_trap(sb, ofs->indexdir, &ofs->indexdir_trap,
"indexdir"); "indexdir");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment