From owner-dev-commits-src-branches@freebsd.org Mon Feb 1 12:41:15 2021 Return-Path: Delivered-To: dev-commits-src-branches@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 204B54FEF5C; Mon, 1 Feb 2021 12:41:15 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4DTncS6CCYz3F74; Mon, 1 Feb 2021 12:41:12 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 6CC881B70E; Mon, 1 Feb 2021 12:41:08 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 111Cf8Up098502; Mon, 1 Feb 2021 12:41:08 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 111Cf89L098500; Mon, 1 Feb 2021 12:41:08 GMT (envelope-from git) Date: Mon, 1 Feb 2021 12:41:08 GMT Message-Id: <202102011241.111Cf89L098500@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Mateusz Guzik Subject: git: 006ec2ed1565 - stable/13 - cache: add trailing slash support MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: mjg X-Git-Repository: src X-Git-Refname: refs/heads/stable/13 X-Git-Reftype: branch X-Git-Commit: 006ec2ed15650778c36ef956c5058b5b38717de7 Auto-Submitted: auto-generated X-BeenThere: dev-commits-src-branches@freebsd.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Commits to the stable branches of the FreeBSD src repository List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 01 Feb 2021 12:41:15 -0000 The branch stable/13 has been updated by mjg: URL: https://cgit.FreeBSD.org/src/commit/?id=006ec2ed15650778c36ef956c5058b5b38717de7 commit 006ec2ed15650778c36ef956c5058b5b38717de7 Author: Mateusz Guzik AuthorDate: 2021-01-26 00:38:21 +0000 Commit: Mateusz Guzik 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. */