cache: add trailing slash support

Tested by:	pho
This commit is contained in:
Mateusz Guzik 2021-01-26 01:38:21 +01:00
parent 8cbd164a17
commit e027e24bfa

View File

@ -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.
*/