Re: [PATCH 01/20] ovl: simplify an error path in ovl_copy_up_workdir()

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

 



On Fri, Jul 11, 2025 at 1:21 AM NeilBrown <neil@xxxxxxxxxx> wrote:
>
> If ovl_copy_up_data() fails the error is not immediately handled but the
> code continues on to call ovl_start_write() and lock_rename(),
> presumably because both of these locks are needed for the cleanup.
> On then (if the lock was successful) is the error checked.
>
> This makes the code a little hard to follow and could be fragile.
>
> This patch changes to handle the error immediately.  A new
> ovl_cleanup_unlocked() is created which takes the required directory
> lock (though it doesn't take the write lock on the filesystem).  This
> will be used extensively in later patches.
>
> In general we need to check the parent is still correct after taking the
> lock (as ovl_copy_up_workdir() does after a successful lock_rename()) so
> that is included in ovl_cleanup_unlocked() using new lock_parent() and
> unlock_parent() calls (it is planned to move this API into VFS code
> eventually, though in a slightly different form).

Since you are not planning to move it to VFS with this name
AND since I assume you want to merge this ovl cleanup prior
to the rest of of patches, please use an ovl helper without
the ovl_ namespace prefix and you have a typo above
its parent_lock() not lock_parent().

And apropos lock helper names, at the tip of your branch
the lock helpers used in ovl_cleanup() are named:
lock_and_check_dentry()/dentry_unlock()

I have multiple comments on your choice of names for those helpers:
1. Please use a consistent name pattern for lock/unlock.
    The pattern <obj-or-lock-type>_{lock,unlock}_* is far more common
    then the pattern lock_<obj-or-lock-type> in the kernel, but at least
    be consistent with dentry_lock_and_check() or better yet
    parent_lock() and later parent_lock_get_child()
2. dentry_unlock() is a very strange name for a helper that
    unlocks the parent. The fact that you document what it does
    in Kernel-doc does not stop people reading the code using it
    from being confused and writing bugs.
3. Why not call it parnet_unlock() like I suggested and like you
    used in this patch set and why not introduce it in VFS to begin with?
    For that matter parent_unlock_{put,return}_child() is more clear IMO.
4. The name dentry_unlock_rename(&rd) also does not balance nicely with
    the name lookup_and_lock_rename(&rd) and has nothing to do with the
    dentry_ prefix. How about lookup_done_and_unlock_rename(&rd)?

Hope this is not too much complaining for review of a small cleanup patch :-p

>
> A fresh cleanup block is added which doesn't share code with other
> cleanup blocks.  It will get a new users in the next patch.
>
> Signed-off-by: NeilBrown <neil@xxxxxxxxxx>
> ---
>  fs/overlayfs/copy_up.c   | 12 ++++++++++--
>  fs/overlayfs/dir.c       | 15 +++++++++++++++
>  fs/overlayfs/overlayfs.h |  6 ++++++
>  fs/overlayfs/util.c      | 10 ++++++++++
>  4 files changed, 41 insertions(+), 2 deletions(-)
>
> diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
> index 8a3c0d18ec2e..5d21b8d94a0a 100644
> --- a/fs/overlayfs/copy_up.c
> +++ b/fs/overlayfs/copy_up.c
> @@ -794,6 +794,9 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
>          */
>         path.dentry = temp;
>         err = ovl_copy_up_data(c, &path);
> +       if (err)
> +               goto cleanup_need_write;
> +
>         /*
>          * We cannot hold lock_rename() throughout this helper, because of
>          * lock ordering with sb_writers, which shouldn't be held when calling
> @@ -809,8 +812,6 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
>                 if (IS_ERR(trap))
>                         goto out;
>                 goto unlock;
> -       } else if (err) {
> -               goto cleanup;
>         }
>
>         err = ovl_copy_up_metadata(c, temp);
> @@ -857,6 +858,13 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
>         ovl_cleanup(ofs, wdir, temp);
>         dput(temp);
>         goto unlock;
> +
> +cleanup_need_write:
> +       ovl_start_write(c->dentry);
> +       ovl_cleanup_unlocked(ofs, c->workdir, temp);
> +       ovl_end_write(c->dentry);
> +       dput(temp);
> +       return err;
>  }
>

Sorry, I will not accept more messy goto routines.
I rewrote your simplification based on the tip of your branch.
Much simpler and no need for this extra routine.
Just always use ovl_cleanup_unlocked() in this function and
ovl_start_write() before goto cleanup_unlocked:

--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -794,13 +794,16 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
         */
        path.dentry = temp;
        err = ovl_copy_up_data(c, &path);
+       ovl_start_write(c->dentry);
+       if (err)
+               goto cleanup_unlocked;
+
        /*
         * We cannot hold lock_rename() throughout this helper, because of
         * lock ordering with sb_writers, which shouldn't be held when calling
         * ovl_copy_up_data(), so lock workdir and destdir and make sure that
         * temp wasn't moved before copy up completion or cleanup.
         */
-       ovl_start_write(c->dentry);
        trap = lock_rename(c->workdir, c->destdir);
        if (trap || temp->d_parent != c->workdir) {
                /* temp or workdir moved underneath us? abort without cleanup */
@@ -809,8 +812,6 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
                if (IS_ERR(trap))
                        goto out;
                goto unlock;
-       } else if (err) {
-               goto cleanup;
        }

        err = ovl_copy_up_metadata(c, temp);
@@ -846,17 +847,17 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
        ovl_inode_update(inode, temp);
        if (S_ISDIR(inode->i_mode))
                ovl_set_flag(OVL_WHITEOUTS, inode);
-unlock:
-       unlock_rename(c->workdir, c->destdir);
 out:
        ovl_end_write(c->dentry);

        return err;

 cleanup:
-       ovl_cleanup(ofs, wdir, temp);
+       unlock_rename(c->workdir, c->destdir);
+cleanup_unlocked:
+       ovl_cleanup_unlocked(ofs, wdir, temp);
        dput(temp);
-       goto unlock;
+       goto out;
 }
---

>  /* Copyup using O_TMPFILE which does not require cross dir locking */
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index 4fc221ea6480..cee35d69e0e6 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -43,6 +43,21 @@ int ovl_cleanup(struct ovl_fs *ofs, struct inode *wdir, struct dentry *wdentry)
>         return err;
>  }
>
> +int ovl_cleanup_unlocked(struct ovl_fs *ofs, struct dentry *workdir,
> +                        struct dentry *wdentry)
> +{
> +       int err;
> +
> +       err = parent_lock(workdir, wdentry);
> +       if (err)
> +               return err;
> +
> +       ovl_cleanup(ofs, workdir->d_inode, wdentry);
> +       parent_unlock(workdir);
> +
> +       return err;
> +}
> +
>  struct dentry *ovl_lookup_temp(struct ovl_fs *ofs, struct dentry *workdir)
>  {
>         struct dentry *temp;
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index 42228d10f6b9..68dc78c712a8 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -416,6 +416,11 @@ static inline bool ovl_open_flags_need_copy_up(int flags)
>  }
>
>  /* util.c */
> +int parent_lock(struct dentry *parent, struct dentry *child);
> +static inline void parent_unlock(struct dentry *parent)
> +{
> +       inode_unlock(parent->d_inode);
> +}

ovl_parent_unlock() or move to vfs please.

>  int ovl_get_write_access(struct dentry *dentry);
>  void ovl_put_write_access(struct dentry *dentry);
>  void ovl_start_write(struct dentry *dentry);
> @@ -843,6 +848,7 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs,
>                                struct inode *dir, struct dentry *newdentry,
>                                struct ovl_cattr *attr);
>  int ovl_cleanup(struct ovl_fs *ofs, struct inode *dir, struct dentry *dentry);
> +int ovl_cleanup_unlocked(struct ovl_fs *ofs, struct dentry *workdir, struct dentry *dentry);
>  struct dentry *ovl_lookup_temp(struct ovl_fs *ofs, struct dentry *workdir);
>  struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
>                                struct ovl_cattr *attr);
> diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
> index 2b4754c645ee..a5105d68f6b4 100644
> --- a/fs/overlayfs/util.c
> +++ b/fs/overlayfs/util.c
> @@ -1544,3 +1544,13 @@ void ovl_copyattr(struct inode *inode)
>         i_size_write(inode, i_size_read(realinode));
>         spin_unlock(&inode->i_lock);
>  }
> +
> +int parent_lock(struct dentry *parent, struct dentry *child)
> +{
> +       inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
> +       if (!child || child->d_parent == parent)
> +               return 0;
> +
> +       inode_unlock(parent->d_inode);
> +       return -EINVAL;
> +}

ovl_parent_lock() or move to vfs please.

Thanks,
Amir.





[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