git: 006ec2ed1565 - stable/13 - cache: add trailing slash support

Mateusz Guzik mjg at FreeBSD.org
Mon Feb 1 12:41:15 UTC 2021


The branch stable/13 has been updated by mjg:

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

commit 006ec2ed15650778c36ef956c5058b5b38717de7
Author:     Mateusz Guzik <mjg at FreeBSD.org>
AuthorDate: 2021-01-26 00:38:21 +0000
Commit:     Mateusz Guzik <mjg at FreeBSD.org>
CommitDate: 2021-02-01 12:39:19 +0000

    cache: add trailing slash support
    
    Tested by:      pho
    
    (cherry picked from commit e027e24bfac7dd311ddacaec73d6c42102069511)
---
 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.
  */


More information about the dev-commits-src-all mailing list