cache: add trailing slash support
Tested by: pho
This commit is contained in:
parent
8cbd164a17
commit
e027e24bfa
@ -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.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user