diff --git a/sys/fs/nfs/nfs_var.h b/sys/fs/nfs/nfs_var.h index 0e826a2b6524..1cf792edeced 100644 --- a/sys/fs/nfs/nfs_var.h +++ b/sys/fs/nfs/nfs_var.h @@ -391,7 +391,7 @@ int nfsv4_fillattr(struct nfsrv_descript *, struct mount *, vnode_t, NFSACL_T *, struct vattr *, fhandle_t *, int, nfsattrbit_t *, struct ucred *, NFSPROC_T *, int, int, int, int, uint64_t, struct statfs *); void nfsrv_fillattr(struct nfsrv_descript *, struct nfsvattr *); -void nfsrv_adj(struct mbuf *, int, int); +struct mbuf *nfsrv_adj(struct mbuf *, int, int); void nfsrv_postopattr(struct nfsrv_descript *, int, struct nfsvattr *); int nfsd_errmap(struct nfsrv_descript *); void nfsv4_uidtostr(uid_t, u_char **, int *); diff --git a/sys/fs/nfsserver/nfs_nfsdport.c b/sys/fs/nfsserver/nfs_nfsdport.c index 050ca6df7aa8..fbe77fcaf9b6 100644 --- a/sys/fs/nfsserver/nfs_nfsdport.c +++ b/sys/fs/nfsserver/nfs_nfsdport.c @@ -757,7 +757,12 @@ nfsvno_readlink(struct vnode *vp, struct ucred *cred, struct thread *p, if (uiop->uio_resid > 0) { len -= uiop->uio_resid; tlen = NFSM_RNDUP(len); - nfsrv_adj(mp3, NFS_MAXPATHLEN - tlen, tlen - len); + if (tlen == 0) { + m_freem(mp3); + mp3 = mp = NULL; + } else if (tlen != NFS_MAXPATHLEN || tlen != len) + mp = nfsrv_adj(mp3, NFS_MAXPATHLEN - tlen, + tlen - len); } *lenp = len; *mpp = mp3; @@ -872,9 +877,9 @@ nfsvno_read(struct vnode *vp, off_t off, int cnt, struct ucred *cred, tlen = NFSM_RNDUP(cnt); if (tlen == 0) { m_freem(m3); - m3 = NULL; + m3 = m = NULL; } else if (len != tlen || tlen != cnt) - nfsrv_adj(m3, len - tlen, tlen - cnt); + m = nfsrv_adj(m3, len - tlen, tlen - cnt); *mpp = m3; *mpendp = m; @@ -6247,7 +6252,11 @@ nfsvno_getxattr(struct vnode *vp, char *name, uint32_t maxresp, tlen = NFSM_RNDUP(len); if (alen != tlen) printf("nfsvno_getxattr: weird size read\n"); - nfsrv_adj(m, alen - tlen, tlen - len); + if (tlen == 0) { + m_freem(m); + m = m2 = NULL; + } else if (alen != tlen || tlen != len) + m2 = nfsrv_adj(m, alen - tlen, tlen - len); } *lenp = len; *mpp = m; diff --git a/sys/fs/nfsserver/nfs_nfsdserv.c b/sys/fs/nfsserver/nfs_nfsdserv.c index 09aa87ae2e2c..1b34cb27dd01 100644 --- a/sys/fs/nfsserver/nfs_nfsdserv.c +++ b/sys/fs/nfsserver/nfs_nfsdserv.c @@ -690,9 +690,11 @@ nfsrvd_readlink(struct nfsrv_descript *nd, __unused int isdgram, goto out; NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED); *tl = txdr_unsigned(len); - nd->nd_mb->m_next = mp; - nd->nd_mb = mpend; - nd->nd_bpos = mtod(mpend, caddr_t) + mpend->m_len; + if (mp != NULL) { + nd->nd_mb->m_next = mp; + nd->nd_mb = mpend; + nd->nd_bpos = mtod(mpend, caddr_t) + mpend->m_len; + } out: NFSEXITCODE2(0, nd); diff --git a/sys/fs/nfsserver/nfs_nfsdsubs.c b/sys/fs/nfsserver/nfs_nfsdsubs.c index 17349343d5e0..428751372121 100644 --- a/sys/fs/nfsserver/nfs_nfsdsubs.c +++ b/sys/fs/nfsserver/nfs_nfsdsubs.c @@ -1268,62 +1268,100 @@ static short *nfsrv_v4errmap[] = { }; /* - * A fiddled version of m_adj() that ensures null fill to a long - * boundary and only trims off the back end + * Trim tlen bytes off the end of the mbuf list and then ensure + * the end of the last mbuf is nul filled to a long boundary, + * as indicated by the value of "nul". + * Return the last mbuf in the updated list and free and mbufs + * that follow it in the original list. + * This is somewhat different than the old nfsrv_adj() with + * support for ext_pgs mbufs. It frees the remaining mbufs + * instead of setting them 0 length, since lists of ext_pgs + * mbufs are all expected to be non-empty. */ -void +struct mbuf * nfsrv_adj(struct mbuf *mp, int len, int nul) { - struct mbuf *m; - int count, i; + struct mbuf *m, *m2; + vm_page_t pg; + int i, lastlen, pgno, plen, tlen, trim; + uint16_t off; char *cp; /* - * Trim from tail. Scan the mbuf chain, - * calculating its length and finding the last mbuf. - * If the adjustment only affects this mbuf, then just - * adjust and return. Otherwise, rescan and truncate - * after the remaining size. + * Find the last mbuf after adjustment and + * how much it needs to be adjusted by. */ - count = 0; + tlen = 0; m = mp; for (;;) { - count += m->m_len; + tlen += m->m_len; if (m->m_next == NULL) break; m = m->m_next; } - if (m->m_len > len) { - m->m_len -= len; - if (nul > 0) { - cp = mtod(m, caddr_t) + m->m_len - nul; - for (i = 0; i < nul; i++) - *cp++ = '\0'; + /* m is now the last mbuf and tlen the total length. */ + + if (len >= m->m_len) { + /* Need to trim away the last mbuf(s). */ + i = tlen - len; + m = mp; + for (;;) { + if (m->m_len >= i) + break; + i -= m->m_len; + m = m->m_next; } - return; - } - count -= len; - if (count < 0) - count = 0; + lastlen = i; + } else + lastlen = m->m_len - len; + /* - * Correct length for chain is "count". - * Find the mbuf with last data, adjust its length, - * and toss data from remaining mbufs on chain. + * m is now the last mbuf after trimming and its length needs to + * be lastlen. + * Adjust the last mbuf and set cp to point to where nuls must be + * written. */ - for (m = mp; m; m = m->m_next) { - if (m->m_len >= count) { - m->m_len = count; - if (nul > 0) { - cp = mtod(m, caddr_t) + m->m_len - nul; - for (i = 0; i < nul; i++) - *cp++ = '\0'; + if ((m->m_flags & M_EXTPG) != 0) { + pgno = m->m_epg_npgs - 1; + off = (pgno == 0) ? m->m_epg_1st_off : 0; + plen = m_epg_pagelen(m, pgno, off); + if (m->m_len > lastlen) { + /* Trim this mbuf. */ + trim = m->m_len - lastlen; + while (trim >= plen) { + KASSERT(pgno > 0, + ("nfsrv_adj: freeing page 0")); + /* Free page. */ + pg = PHYS_TO_VM_PAGE(m->m_epg_pa[pgno]); + vm_page_unwire_noq(pg); + vm_page_free(pg); + trim -= plen; + m->m_epg_npgs--; + pgno--; + off = (pgno == 0) ? m->m_epg_1st_off : 0; + plen = m_epg_pagelen(m, pgno, off); } - break; + plen -= trim; + m->m_epg_last_len = plen; + m->m_len = lastlen; } - count -= m->m_len; + cp = (char *)(void *)PHYS_TO_DMAP(m->m_epg_pa[pgno]); + cp += off + plen - nul; + } else { + m->m_len = lastlen; + cp = mtod(m, char *) + m->m_len - nul; } - for (m = m->m_next; m; m = m->m_next) - m->m_len = 0; + + /* Write the nul bytes. */ + for (i = 0; i < nul; i++) + *cp++ = '\0'; + + /* Free up any mbufs past "m". */ + m2 = m->m_next; + m->m_next = NULL; + if (m2 != NULL) + m_freem(m2); + return (m); } /*