The branch main has been updated by mjg:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=e027e24bfac7dd311ddacaec73d6c42102069511

commit e027e24bfac7dd311ddacaec73d6c42102069511
Author:     Mateusz Guzik <m...@freebsd.org>
AuthorDate: 2021-01-26 00:38:21 +0000
Commit:     Mateusz Guzik <m...@freebsd.org>
CommitDate: 2021-01-31 12:02:46 +0000

    cache: add trailing slash support
    
    Tested by:      pho
---
 sys/kern/vfs_cache.c | 227 +++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 184 insertions(+), 43 deletions(-)

diff --git a/sys/kern/vfs_cache.c b/sys/kern/vfs_cache.c
index b8b876657211..1780e8235dd3 100644
--- a/sys/kern/vfs_cache.c
+++ b/sys/kern/vfs_cache.c
@@ -3714,6 +3714,7 @@ static bool cache_fplookup_is_mp(struct cache_fpl *fpl);
 static int cache_fplookup_cross_mount(struct cache_fpl *fpl);
 static int cache_fplookup_partial_setup(struct cache_fpl *fpl);
 static int cache_fplookup_skip_slashes(struct cache_fpl *fpl);
+static int cache_fplookup_trailingslash(struct cache_fpl *fpl);
 static int cache_fplookup_preparse(struct cache_fpl *fpl);
 static void cache_fpl_pathlen_dec(struct cache_fpl *fpl);
 static void cache_fpl_pathlen_inc(struct cache_fpl *fpl);
@@ -3971,6 +3972,13 @@ cache_fpl_islastcn(struct nameidata *ndp)
        return (*ndp->ni_next == 0);
 }
 
+static bool
+cache_fpl_istrailingslash(struct cache_fpl *fpl)
+{
+
+       return (*(fpl->nulchar - 1) == '/');
+}
+
 static bool
 cache_fpl_isdotdot(struct componentname *cnp)
 {
@@ -4201,6 +4209,15 @@ cache_fplookup_final_modifying(struct cache_fpl *fpl)
        if (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)
                docache = false;
 
+       /*
+        * Regular lookup nulifies the slash, which we don't do here.
+        * Don't take chances with filesystem routines seeing it for
+        * the last entry.
+        */
+       if (cache_fpl_istrailingslash(fpl)) {
+               return (cache_fpl_partial(fpl));
+       }
+
        mp = atomic_load_ptr(&dvp->v_mount);
        if (__predict_false(mp == NULL)) {
                return (cache_fpl_aborted(fpl));
@@ -4533,7 +4550,6 @@ cache_fplookup_noentry(struct cache_fpl *fpl)
        dvp = fpl->dvp;
        dvp_seqc = fpl->dvp_seqc;
 
-       MPASS(*(cnp->cn_nameptr) != '/');
        MPASS((cnp->cn_flags & MAKEENTRY) == 0);
        MPASS((cnp->cn_flags & ISDOTDOT) == 0);
        MPASS(!cache_fpl_isdotdot(cnp));
@@ -4546,6 +4562,14 @@ cache_fplookup_noentry(struct cache_fpl *fpl)
                return (cache_fpl_handled_error(fpl, ENAMETOOLONG));
        }
 
+       if (cnp->cn_nameptr[0] == '/') {
+               return (cache_fplookup_skip_slashes(fpl));
+       }
+
+       if (cnp->cn_nameptr[0] == '\0') {
+               return (cache_fplookup_trailingslash(fpl));
+       }
+
        if (cnp->cn_nameiop != LOOKUP) {
                fpl->tvp = NULL;
                return (cache_fplookup_modifying(fpl));
@@ -4562,6 +4586,15 @@ cache_fplookup_noentry(struct cache_fpl *fpl)
                return (cache_fpl_partial(fpl));
        }
 
+       /*
+        * Regular lookup nulifies the slash, which we don't do here.
+        * Don't take chances with filesystem routines seeing it for
+        * the last entry.
+        */
+       if (cache_fpl_istrailingslash(fpl)) {
+               return (cache_fpl_partial(fpl));
+       }
+
        /*
         * Secure access to dvp; check cache_fplookup_partial_setup for
         * reasoning.
@@ -4792,6 +4825,7 @@ cache_symlink_resolve(struct cache_fpl *fpl, const char 
*string, size_t len)
 {
        struct nameidata *ndp;
        struct componentname *cnp;
+       size_t adjust;
 
        ndp = fpl->ndp;
        cnp = fpl->cnp;
@@ -4800,6 +4834,12 @@ cache_symlink_resolve(struct cache_fpl *fpl, const char 
*string, size_t len)
                return (ENOENT);
        }
 
+       if (__predict_false(len > MAXPATHLEN - 2)) {
+               if (cache_fpl_istrailingslash(fpl)) {
+                       return (EAGAIN);
+               }
+       }
+
        ndp->ni_pathlen = fpl->nulchar - cnp->cn_nameptr - cnp->cn_namelen + 1;
 #ifdef INVARIANTS
        if (ndp->ni_pathlen != fpl->debug.ni_pathlen) {
@@ -4817,15 +4857,22 @@ cache_symlink_resolve(struct cache_fpl *fpl, const char 
*string, size_t len)
                return (ELOOP);
        }
 
+       adjust = len;
        if (ndp->ni_pathlen > 1) {
                bcopy(ndp->ni_next, cnp->cn_pnbuf + len, ndp->ni_pathlen);
        } else {
-               cnp->cn_pnbuf[len] = '\0';
+               if (cache_fpl_istrailingslash(fpl)) {
+                       adjust = len + 1;
+                       cnp->cn_pnbuf[len] = '/';
+                       cnp->cn_pnbuf[len + 1] = '\0';
+               } else {
+                       cnp->cn_pnbuf[len] = '\0';
+               }
        }
        bcopy(string, cnp->cn_pnbuf, len);
 
-       ndp->ni_pathlen += len;
-       cache_fpl_pathlen_add(fpl, len);
+       ndp->ni_pathlen += adjust;
+       cache_fpl_pathlen_add(fpl, adjust);
        cnp->cn_nameptr = cnp->cn_pnbuf;
        fpl->nulchar = &cnp->cn_nameptr[ndp->ni_pathlen - 1];
 
@@ -4925,9 +4972,6 @@ cache_fplookup_next(struct cache_fpl *fpl)
        }
 
        if (__predict_false(ncp == NULL)) {
-               if (cnp->cn_nameptr[0] == '/') {
-                       return (cache_fplookup_skip_slashes(fpl));
-               }
                return (cache_fplookup_noentry(fpl));
        }
 
@@ -5194,21 +5238,6 @@ cache_fplookup_preparse(struct cache_fpl *fpl)
            ("%s: mismatch on string (%p != %p) [%s]\n", __func__,
            &cnp->cn_nameptr[fpl->debug.ni_pathlen - 2], fpl->nulchar - 1,
            cnp->cn_pnbuf));
-       if (__predict_false(*(fpl->nulchar - 1) == '/')) {
-               /*
-                * TODO
-                * Regular lookup performs the following:
-                * *ndp->ni_next = '\0';
-                * cnp->cn_flags |= TRAILINGSLASH;
-                *
-                * Which is problematic since it modifies data read
-                * from userspace. Then if fast path lookup was to
-                * abort we would have to either restore it or convey
-                * the flag. Since this is a corner case just ignore
-                * it for simplicity.
-                */
-               return (cache_fpl_aborted(fpl));
-       }
        return (0);
 }
 
@@ -5254,27 +5283,6 @@ cache_fplookup_parse(struct cache_fpl *fpl)
         * then it could not have been too long to begin with.
         */
        ndp->ni_next = cp;
-
-#ifdef INVARIANTS
-       /*
-        * Code below is only here to assure compatibility with regular lookup.
-        * It covers handling of trailing slashes and names like "/", both of
-        * which of can be taken care of upfront which lockless lookup does
-        * in cache_fplookup_preparse. Regular lookup performs these for each
-        * path component.
-        */
-       while (*cp == '/' && (cp[1] == '/' || cp[1] == '\0')) {
-               cp++;
-               if (*cp == '\0') {
-                       panic("%s: ran into TRAILINGSLASH handling from [%s]\n",
-                           __func__, cnp->cn_pnbuf);
-               }
-       }
-
-       if (cnp->cn_nameptr[0] == '\0') {
-               panic("%s: ran into degenerate name from [%s]\n", __func__, 
cnp->cn_pnbuf);
-       }
-#endif
 }
 
 static void
@@ -5339,6 +5347,139 @@ cache_fplookup_skip_slashes(struct cache_fpl *fpl)
        return (0);
 }
 
+/*
+ * Handle trailing slashes (e.g., "foo/").
+ *
+ * If a trailing slash is found the terminal vnode must be a directory.
+ * Regular lookup shortens the path by nulifying the first trailing slash and
+ * sets the TRAILINGSLASH flag to denote this took place. There are several
+ * checks on it performed later.
+ *
+ * Similarly to spurious slashes, lockless lookup handles this in a speculative
+ * manner relying on an invariant that a non-directory vnode will get a miss.
+ * In this case cn_nameptr[0] == '\0' and cn_namelen == 0.
+ *
+ * Thus for a path like "foo/bar/" the code unwinds the state back to 'bar/'
+ * and denotes this is the last path component, which avoids looping back.
+ *
+ * Only plain lookups are supported for now to restrict corner cases to handle.
+ */
+static int __noinline
+cache_fplookup_trailingslash(struct cache_fpl *fpl)
+{
+#ifdef INVARIANTS
+       size_t ni_pathlen;
+#endif
+       struct nameidata *ndp;
+       struct componentname *cnp;
+       struct namecache *ncp;
+       struct vnode *tvp;
+       char *cn_nameptr_orig, *cn_nameptr_slash;
+       seqc_t tvp_seqc;
+       u_char nc_flag;
+
+       ndp = fpl->ndp;
+       cnp = fpl->cnp;
+       tvp = fpl->tvp;
+       tvp_seqc = fpl->tvp_seqc;
+
+       MPASS(fpl->dvp == fpl->tvp);
+       KASSERT(cache_fpl_istrailingslash(fpl),
+           ("%s: expected trailing slash at %p; string [%s]\n", __func__, 
fpl->nulchar - 1,
+           cnp->cn_pnbuf));
+       KASSERT(cnp->cn_nameptr[0] == '\0',
+           ("%s: expected nul char at %p; string [%s]\n", __func__, 
&cnp->cn_nameptr[0],
+           cnp->cn_pnbuf));
+       KASSERT(cnp->cn_namelen == 0,
+           ("%s: namelen 0 but got %ld; string [%s]\n", __func__, 
cnp->cn_namelen,
+           cnp->cn_pnbuf));
+       MPASS(cnp->cn_nameptr > cnp->cn_pnbuf);
+
+       if (cnp->cn_nameiop != LOOKUP) {
+               return (cache_fpl_aborted(fpl));
+       }
+
+       if (__predict_false(tvp->v_type != VDIR)) {
+               if (!vn_seqc_consistent(tvp, tvp_seqc)) {
+                       return (cache_fpl_aborted(fpl));
+               }
+               cache_fpl_smr_exit(fpl);
+               return (cache_fpl_handled_error(fpl, ENOTDIR));
+       }
+
+       /*
+        * Denote the last component.
+        */
+       ndp->ni_next = &cnp->cn_nameptr[0];
+       MPASS(cache_fpl_islastcn(ndp));
+
+       /*
+        * Unwind trailing slashes.
+        */
+       cn_nameptr_orig = cnp->cn_nameptr;
+       while (cnp->cn_nameptr >= cnp->cn_pnbuf) {
+               cnp->cn_nameptr--;
+               if (cnp->cn_nameptr[0] != '/') {
+                       break;
+               }
+       }
+
+       /*
+        * Unwind to the beginning of the path component.
+        *
+        * Note the path may or may not have started with a slash.
+        */
+       cn_nameptr_slash = cnp->cn_nameptr;
+       while (cnp->cn_nameptr > cnp->cn_pnbuf) {
+               cnp->cn_nameptr--;
+               if (cnp->cn_nameptr[0] == '/') {
+                       break;
+               }
+       }
+       if (cnp->cn_nameptr[0] == '/') {
+               cnp->cn_nameptr++;
+       }
+
+       cnp->cn_namelen = cn_nameptr_slash - cnp->cn_nameptr + 1;
+       cache_fpl_pathlen_add(fpl, cn_nameptr_orig - cnp->cn_nameptr);
+       cache_fpl_checkpoint(fpl);
+
+#ifdef INVARIANTS
+       ni_pathlen = fpl->nulchar - cnp->cn_nameptr + 1;
+       if (ni_pathlen != fpl->debug.ni_pathlen) {
+               panic("%s: mismatch (%zu != %zu) nulchar %p nameptr %p [%s] ; 
full string [%s]\n",
+                   __func__, ni_pathlen, fpl->debug.ni_pathlen, fpl->nulchar,
+                   cnp->cn_nameptr, cnp->cn_nameptr, cnp->cn_pnbuf);
+       }
+#endif
+
+       /*
+        * The previous directory is this one.
+        */
+       if (cnp->cn_nameptr[0] == '.' && cnp->cn_namelen == 1) {
+               return (0);
+       }
+
+       /*
+        * The previous directory is something else.
+        */
+       tvp = fpl->tvp;
+       ncp = atomic_load_consume_ptr(&tvp->v_cache_dd);
+       if (__predict_false(ncp == NULL)) {
+               return (cache_fpl_aborted(fpl));
+       }
+       nc_flag = atomic_load_char(&ncp->nc_flag);
+       if ((nc_flag & NCF_ISDOTDOT) != 0) {
+               return (cache_fpl_aborted(fpl));
+       }
+       fpl->dvp = ncp->nc_dvp;
+       fpl->dvp_seqc = vn_seqc_read_any(fpl->dvp);
+       if (seqc_in_modify(fpl->dvp_seqc)) {
+               return (cache_fpl_aborted(fpl));
+       }
+       return (0);
+}
+
 /*
  * See the API contract for VOP_FPLOOKUP_VEXEC.
  */
_______________________________________________
dev-commits-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/dev-commits-src-all
To unsubscribe, send any mail to "dev-commits-src-all-unsubscr...@freebsd.org"

Reply via email to