Re: [PATCH] ovl: check for incomapt features in work dir

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sun, Aug 30, 2020 at 11:06 PM Amir Goldstein <amir73il@xxxxxxxxx> wrote:
>
> 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: Amir Goldstein <amir73il@xxxxxxxxx>
> ---
>
> Vivek,
>
> I dusted off some code from my old ovl-features branch.
> This should explain what I was trying to say on review of volatile (v6).
>
> There are also some helpers on that branch to create incompat feature dir,
> but they seemed too generic for your needs, so I left them out.
>
> What I was trying to explain is that with this change, the documentation
> for "volatile" should intstruct user to remove the incompat/volatile dir
> entirely, not just the dirty file, in order to reuse the overlay and I
> think that makes sense.
>
> To be clear, an empty work/incompat dir does not prevent neither old nor
> new kernel from mounting the overlay and an empty incompat/volatile dir
> does not prevent old kernel from mounting the overlay, which is why we
> still need to create the dirty file.
>
> Thanks,
> Amir.
>
>
>  fs/overlayfs/overlayfs.h |  4 +++-
>  fs/overlayfs/readdir.c   | 31 +++++++++++++++++++++++++------
>  fs/overlayfs/super.c     | 25 ++++++++++++++++++-------
>  3 files changed, 46 insertions(+), 14 deletions(-)
>
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index 29bc1ec699e7..dc8ad8dc884a 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -31,6 +31,8 @@ enum ovl_path_type {
>  #define OVL_XATTR_UPPER OVL_XATTR_PREFIX "upper"
>  #define OVL_XATTR_METACOPY OVL_XATTR_PREFIX "metacopy"
>
> +#define OVL_INCOMPATDIR_NAME "incompat"
> +
>  enum ovl_inode_flag {
>         /* Pure upper dir that may contain non pure upper entries */
>         OVL_IMPURE,
> @@ -54,6 +56,7 @@ enum {
>         OVL_XINO_ON,
>  };
>
> +
>  /*
>   * The tuple (fh,uuid) is a universal unique identifier for a copy up origin,
>   * where:
> @@ -349,7 +352,6 @@ static inline void ovl_inode_unlock(struct inode *inode)
>         mutex_unlock(&OVL_I(inode)->lock);
>  }
>
> -
>  /* namei.c */
>  int ovl_check_fb_len(struct ovl_fb *fb, int fb_len);
>
> diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
> index 6918b98faeb6..8e4938b51814 100644
> --- a/fs/overlayfs/readdir.c
> +++ b/fs/overlayfs/readdir.c
> @@ -1051,7 +1051,8 @@ 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)
> +static int ovl_workdir_cleanup_recurse(struct path *path, int level,
> +                                      bool incompat)
>  {
>         int err;
>         struct inode *dir = path->dentry->d_inode;
> @@ -1079,25 +1080,42 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level)
>                                 continue;
>                         if (p->len == 2 && p->name[1] == '.')
>                                 continue;
> +               } else if (incompat) {
> +                       pr_warn("overlay with incompat feature '%.*s' cannot be mounted\n",
> +                               p->len, p->name);
> +                       err = -EEXIST;
> +                       break;
>                 }
>                 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);
> +                       err = ovl_workdir_cleanup(dir, path->mnt, dentry, level);
>                 dput(dentry);
> +               if (err)
> +                       break;
>         }
>         inode_unlock(dir);
>  out:
>         ovl_cache_free(&list);
> +       return err;
>  }
>
>  int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt,
> -                        struct dentry *dentry, int level)
> +                       struct dentry *dentry, int level)
>  {
> +       bool incompat = false;
>         int err;
>
> -       if (!d_is_dir(dentry) || level > 1) {
> +       /*
> +        * 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.
> +        */
> +       if (d_is_dir(dentry) && level == 1 &&
> +           !strcmp(dentry->d_name.name, OVL_INCOMPATDIR_NAME)) {
> +               incompat = true;

Self nit - move this test into ovl_workdir_cleanup_recurse()
Noticed after posting, will post v2.

> +       } else if (!d_is_dir(dentry) || level > 1) {
>                 return ovl_cleanup(dir, dentry);
>         }
>
> @@ -1106,9 +1124,10 @@ int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt,
>                 struct path path = { .mnt = mnt, .dentry = dentry };
>
>                 inode_unlock(dir);
> -               ovl_workdir_cleanup_recurse(&path, level + 1);
> +               err = ovl_workdir_cleanup_recurse(&path, level + 1, incompat);
>                 inode_lock_nested(dir, I_MUTEX_PARENT);
> -               err = ovl_cleanup(dir, dentry);
> +               if (!err)
> +                       err = ovl_cleanup(dir, dentry);
>         }
>
>         return err;
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index 4b38141c2985..3cd47e4b2eae 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -705,8 +705,12 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
>                                 goto out_unlock;
>
>                         retried = true;
> -                       ovl_workdir_cleanup(dir, mnt, work, 0);
> +                       err = ovl_workdir_cleanup(dir, mnt, work, 0);
>                         dput(work);
> +                       if (err == -EEXIST) {
> +                               work = ERR_PTR(err);
> +                               goto out_unlock;
> +                       }
>                         goto retry;
>                 }
>
> @@ -1203,7 +1207,7 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
>                             struct path *workpath)
>  {
>         struct vfsmount *mnt = ovl_upper_mnt(ofs);
> -       struct dentry *temp;
> +       struct dentry *temp, *workdir;
>         bool rename_whiteout;
>         bool d_type;
>         int fh_type;
> @@ -1213,10 +1217,13 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
>         if (err)
>                 return err;
>
> -       ofs->workdir = ovl_workdir_create(ofs, OVL_WORKDIR_NAME, false);
> -       if (!ofs->workdir)
> +       workdir = ovl_workdir_create(ofs, OVL_WORKDIR_NAME, false);
> +       err = PTR_ERR(workdir);
> +       if (IS_ERR_OR_NULL(workdir))
>                 goto out;
>
> +       ofs->workdir = workdir;
> +
>         err = ovl_setup_trap(sb, ofs->workdir, &ofs->workdir_trap, "workdir");
>         if (err)
>                 goto out;
> @@ -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 vfsmount *mnt = ovl_upper_mnt(ofs);
> +       struct dentry *indexdir;
>         int err;
>
>         err = mnt_want_write(mnt);
> @@ -1366,9 +1374,12 @@ static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs,
>         ofs->workdir_trap = NULL;
>         dput(ofs->workdir);
>         ofs->workdir = NULL;
> -       ofs->indexdir = ovl_workdir_create(ofs, OVL_INDEXDIR_NAME, true);
> -       if (ofs->indexdir) {
> -               ofs->workdir = dget(ofs->indexdir);
> +       indexdir = ovl_workdir_create(ofs, OVL_INDEXDIR_NAME, true);
> +       if (IS_ERR(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,
>                                      "indexdir");
> --
> 2.17.1
>



[Index of Archives]     [Linux Filesystems Devel]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux