Author: kib
Date: Fri Nov 13 09:31:57 2020
New Revision: 367631
URL: https://svnweb.freebsd.org/changeset/base/367631

Log:
  Implement vn_lock_pair().
  
  In collaboration with:        pho
  Reviewed by:  mckusick (previous version), markj (previous version)
  Tested by:    markj (syzkaller), pho
  Sponsored by: The FreeBSD Foundation
  Differential revision:        https://reviews.freebsd.org/D26136

Modified:
  head/sys/kern/vfs_vnops.c
  head/sys/sys/vnode.h

Modified: head/sys/kern/vfs_vnops.c
==============================================================================
--- head/sys/kern/vfs_vnops.c   Fri Nov 13 02:05:45 2020        (r367630)
+++ head/sys/kern/vfs_vnops.c   Fri Nov 13 09:31:57 2020        (r367631)
@@ -70,6 +70,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/filio.h>
 #include <sys/resourcevar.h>
 #include <sys/rwlock.h>
+#include <sys/prng.h>
 #include <sys/sx.h>
 #include <sys/sleepqueue.h>
 #include <sys/sysctl.h>
@@ -275,6 +276,10 @@ restart:
                        vn_finished_write(mp);
                        if (error) {
                                NDFREE(ndp, NDF_ONLY_PNBUF);
+                               if (error == ERELOOKUP) {
+                                       NDREINIT(ndp);
+                                       goto restart;
+                               }
                                return (error);
                        }
                        fmode &= ~O_TRUNC;
@@ -1524,6 +1529,7 @@ vn_truncate(struct file *fp, off_t length, struct ucre
 
        vp = fp->f_vnode;
 
+retry:
        /*
         * Lock the whole range for truncation.  Otherwise split i/o
         * might happen partly before and partly after the truncation.
@@ -1550,6 +1556,8 @@ out:
        vn_finished_write(mp);
 out1:
        vn_rangelock_unlock(vp, rl_cookie);
+       if (error == ERELOOKUP)
+               goto retry;
        return (error);
 }
 
@@ -3317,4 +3325,92 @@ vn_fallocate(struct file *fp, off_t offset, off_t len,
        }
 
        return (error);
+}
+
+static u_long vn_lock_pair_pause_cnt;
+SYSCTL_ULONG(_debug, OID_AUTO, vn_lock_pair_pause, CTLFLAG_RD,
+    &vn_lock_pair_pause_cnt, 0,
+    "Count of vn_lock_pair deadlocks");
+
+static void
+vn_lock_pair_pause(const char *wmesg)
+{
+       atomic_add_long(&vn_lock_pair_pause_cnt, 1);
+       pause(wmesg, prng32_bounded(hz / 10));
+}
+
+/*
+ * Lock pair of vnodes vp1, vp2, avoiding lock order reversal.
+ * vp1_locked indicates whether vp1 is exclusively locked; if not, vp1
+ * must be unlocked.  Same for vp2 and vp2_locked.  One of the vnodes
+ * can be NULL.
+ *
+ * The function returns with both vnodes exclusively locked, and
+ * guarantees that it does not create lock order reversal with other
+ * threads during its execution.  Both vnodes could be unlocked
+ * temporary (and reclaimed).
+ */
+void
+vn_lock_pair(struct vnode *vp1, bool vp1_locked, struct vnode *vp2,
+    bool vp2_locked)
+{
+       int error;
+
+       if (vp1 == NULL && vp2 == NULL)
+               return;
+       if (vp1 != NULL) {
+               if (vp1_locked)
+                       ASSERT_VOP_ELOCKED(vp1, "vp1");
+               else
+                       ASSERT_VOP_UNLOCKED(vp1, "vp1");
+       } else {
+               vp1_locked = true;
+       }
+       if (vp2 != NULL) {
+               if (vp2_locked)
+                       ASSERT_VOP_ELOCKED(vp2, "vp2");
+               else
+                       ASSERT_VOP_UNLOCKED(vp2, "vp2");
+       } else {
+               vp2_locked = true;
+       }
+       if (!vp1_locked && !vp2_locked) {
+               vn_lock(vp1, LK_EXCLUSIVE | LK_RETRY);
+               vp1_locked = true;
+       }
+
+       for (;;) {
+               if (vp1_locked && vp2_locked)
+                       break;
+               if (vp1_locked && vp2 != NULL) {
+                       if (vp1 != NULL) {
+                               error = VOP_LOCK1(vp2, LK_EXCLUSIVE | LK_NOWAIT,
+                                   __FILE__, __LINE__);
+                               if (error == 0)
+                                       break;
+                               VOP_UNLOCK(vp1);
+                               vp1_locked = false;
+                               vn_lock_pair_pause("vlp1");
+                       }
+                       vn_lock(vp2, LK_EXCLUSIVE | LK_RETRY);
+                       vp2_locked = true;
+               }
+               if (vp2_locked && vp1 != NULL) {
+                       if (vp2 != NULL) {
+                               error = VOP_LOCK1(vp1, LK_EXCLUSIVE | LK_NOWAIT,
+                                   __FILE__, __LINE__);
+                               if (error == 0)
+                                       break;
+                               VOP_UNLOCK(vp2);
+                               vp2_locked = false;
+                               vn_lock_pair_pause("vlp2");
+                       }
+                       vn_lock(vp1, LK_EXCLUSIVE | LK_RETRY);
+                       vp1_locked = true;
+               }
+       }
+       if (vp1 != NULL)
+               ASSERT_VOP_ELOCKED(vp1, "vp1 ret");
+       if (vp2 != NULL)
+               ASSERT_VOP_ELOCKED(vp2, "vp2 ret");
 }

Modified: head/sys/sys/vnode.h
==============================================================================
--- head/sys/sys/vnode.h        Fri Nov 13 02:05:45 2020        (r367630)
+++ head/sys/sys/vnode.h        Fri Nov 13 09:31:57 2020        (r367631)
@@ -729,6 +729,8 @@ bool        vn_isdisk_error(struct vnode *vp, int *errp);
 bool   vn_isdisk(struct vnode *vp);
 int    _vn_lock(struct vnode *vp, int flags, const char *file, int line);
 #define vn_lock(vp, flags) _vn_lock(vp, flags, __FILE__, __LINE__)
+void   vn_lock_pair(struct vnode *vp1, bool vp1_locked, struct vnode *vp2,
+           bool vp2_locked);
 int    vn_open(struct nameidata *ndp, int *flagp, int cmode, struct file *fp);
 int    vn_open_cred(struct nameidata *ndp, int *flagp, int cmode,
            u_int vn_open_flags, struct ucred *cred, struct file *fp);
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to