git: e027e24bfac7 - main - cache: add trailing slash support
Mateusz Guzik
mjg at FreeBSD.org
Sun Jan 31 12:03:47 UTC 2021
The branch main has been updated by mjg:
URL: https://cgit.FreeBSD.org/src/commit/?id=e027e24bfac7dd311ddacaec73d6c42102069511
commit e027e24bfac7dd311ddacaec73d6c42102069511
Author: Mateusz Guzik <mjg at FreeBSD.org>
AuthorDate: 2021-01-26 00:38:21 +0000
Commit: Mateusz Guzik <mjg at 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.
*/
More information about the dev-commits-src-main
mailing list