Page MenuHomeFreeBSD

D60603.id189506.diff
No OneTemporary

D60603.id189506.diff

diff --git a/sys/fs/nfsclient/nfs_clbio.c b/sys/fs/nfsclient/nfs_clbio.c
--- a/sys/fs/nfsclient/nfs_clbio.c
+++ b/sys/fs/nfsclient/nfs_clbio.c
@@ -71,6 +71,8 @@
struct thread *td);
static int nfs_directio_write(struct vnode *vp, struct uio *uiop,
struct ucred *cred, int ioflag);
+static int ncl_check_record(struct buf *bp, int pos);
+static int ncl_record_start(struct buf *bp, int on);
/*
* Vnode op for VM getpages.
@@ -437,8 +439,9 @@
struct thread *td;
struct nfsmount *nmp = VFSTONFS(vp->v_mount);
daddr_t lbn, rabn;
- int biosize, bcount, error, i, n, nra, on, save2, seqcount;
+ int biosize, bcount, error, i, n, newon, nra, on, reanchor, save2, seqcount;
off_t tmp_off;
+ bool copied;
KASSERT(uio->uio_rw == UIO_READ, ("ncl_read mode"));
if (uio->uio_resid == 0)
@@ -479,6 +482,7 @@
return error;
save2 = curthread_pflags2_set(TDP2_SBPAGES);
+ copied = false;
do {
u_quad_t nsize;
@@ -679,6 +683,30 @@
goto out;
}
+ /*
+ * A saved per-entry position can point inside a record when
+ * the block is rebuilt from a modified directory. Move
+ * back to the start of the record which contains it so
+ * that only whole records are returned. This does not
+ * preserve the identity of the saved position across
+ * changes to the synthesized layout; entries can be
+ * repeated or skipped. The position is committed only
+ * once a complete record can be copied.
+ */
+ reanchor = 0;
+ if (on > 0 && (bp->b_flags & B_INVAL) == 0) {
+ newon = ncl_record_start(bp, on);
+ if (newon < 0) {
+ brelse(bp);
+ error = EIO;
+ goto out;
+ }
+ if (newon != on) {
+ reanchor = on - newon;
+ on = newon;
+ }
+ }
+
/*
* If not eof and read aheads are enabled, start one.
* (You need the current block first, so that you have the
@@ -722,9 +750,45 @@
* right here.
*/
n = lmin(uio->uio_resid, NFS_DIRBLKSIZ - bp->b_resid - on);
- if (np->n_direofoffset && n > np->n_direofoffset - uio->uio_offset)
- n = np->n_direofoffset - uio->uio_offset;
+ if (np->n_direofoffset &&
+ n > np->n_direofoffset - (uio->uio_offset - reanchor))
+ n = np->n_direofoffset - (uio->uio_offset - reanchor);
NFSUNLOCKNODE(np);
+ /*
+ * Return only whole records. When the position above was
+ * re-anchored, the caller's read may end in the middle of a
+ * record.
+ */
+ if (n > 0) {
+ int pos2, reclen2;
+
+ for (pos2 = 0; pos2 < n; pos2 += reclen2) {
+ reclen2 = ncl_check_record(bp, on + pos2);
+ if (reclen2 < 0) {
+ brelse(bp);
+ error = EIO;
+ goto out;
+ }
+ if (pos2 + reclen2 > n)
+ break;
+ }
+ if (pos2 == 0) {
+ if (!copied) {
+ brelse(bp);
+ error = EINVAL;
+ goto out;
+ }
+ /*
+ * The remaining space is smaller than the
+ * next record; stop here.
+ */
+ n = 0;
+ } else {
+ if (reanchor != 0)
+ uio->uio_offset -= reanchor;
+ n = pos2;
+ }
+ }
break;
default:
printf(" ncl_bioread: type %x unexpected\n", vp->v_type);
@@ -734,6 +798,8 @@
if (n > 0) {
error = vn_io_fault_uiomove(bp->b_data + on, (int)n, uio);
+ if (error == 0)
+ copied = true;
}
if (vp->v_type == VLNK)
n = 0;
@@ -1542,6 +1608,75 @@
return (EIO);
}
+/*
+ * Validate the directory record at byte offset 'pos' of a directory
+ * block and return its length, or -1 when the block is malformed.
+ */
+static int
+ncl_check_record(struct buf *bp, int pos)
+{
+ struct dirent *dp;
+ off_t off;
+
+ if (pos < 0 || bp->b_bcount - pos < (int)offsetof(struct dirent, d_name))
+ return (-1);
+ dp = (struct dirent *)(bp->b_data + pos);
+ off = (off_t)bp->b_lblkno * NFS_DIRBLKSIZ + pos;
+ if (dp->d_reclen < _GENERIC_DIRLEN(dp->d_namlen) ||
+ (dp->d_reclen & (sizeof(uint64_t) - 1)) != 0 ||
+ dp->d_reclen > DIRBLKSIZ - (off & (DIRBLKSIZ - 1)) ||
+ dp->d_reclen > bp->b_bcount - pos)
+ return (-1);
+ return (dp->d_reclen);
+}
+
+/*
+ * Set next-entry offsets in the client's synthesized directory stream.
+ * Successful nonempty readdir RPCs pad the entire buffer; require records
+ * to cover it exactly without crossing DIRBLKSIZ boundaries.
+ */
+static int
+ncl_fix_d_off(struct buf *bp)
+{
+ struct dirent *dp;
+ off_t off;
+ int pos, reclen;
+
+ off = (off_t)bp->b_lblkno * NFS_DIRBLKSIZ;
+ for (pos = 0; pos < bp->b_bcount; pos += reclen) {
+ reclen = ncl_check_record(bp, pos);
+ if (reclen < 0) {
+ printf("ncl_doio: malformed directory block %jd\n",
+ (intmax_t)bp->b_lblkno);
+ return (EIO);
+ }
+ off += reclen;
+ dp = (struct dirent *)(bp->b_data + pos);
+ dp->d_off = off;
+ }
+ return (0);
+}
+
+/*
+ * Return the start of the record which contains the logical offset 'on'
+ * inside a directory block, or -1 when the block is malformed. When
+ * 'on' is already a record start, it is returned unchanged.
+ */
+static int
+ncl_record_start(struct buf *bp, int on)
+{
+ int pos, reclen;
+
+ for (pos = 0; pos < on; pos += reclen) {
+ reclen = ncl_check_record(bp, pos);
+ if (reclen < 0)
+ return (-1);
+ if (pos + reclen > on)
+ break;
+ }
+ return (pos);
+}
+
/*
* Do an I/O operation to/from a cache block. This may be called
* synchronously or from an nfsiod.
@@ -1639,6 +1774,8 @@
*/
if (error == 0 && uiop->uio_resid == bp->b_bcount)
bp->b_flags |= B_INVAL;
+ else if (error == 0)
+ error = ncl_fix_d_off(bp);
break;
default:
printf("ncl_doio: type %x unexpected\n", vp->v_type);
diff --git a/sys/fs/nfsclient/nfs_clvnops.c b/sys/fs/nfsclient/nfs_clvnops.c
--- a/sys/fs/nfsclient/nfs_clvnops.c
+++ b/sys/fs/nfsclient/nfs_clvnops.c
@@ -2606,7 +2606,7 @@
struct vnode *vp = ap->a_vp;
struct nfsnode *np = VTONFS(vp);
struct uio *uio = ap->a_uio;
- ssize_t tresid, left;
+ ssize_t tresid;
int error = 0;
struct vattr vattr;
@@ -2638,15 +2638,13 @@
NFSUNLOCKNODE(np);
/*
- * NFS always guarantees that directory entries don't straddle
- * DIRBLKSIZ boundaries. As such, we need to limit the size
- * to an exact multiple of DIRBLKSIZ, to avoid copying a partial
- * directory entry.
+ * A read may resume at any entry boundary inside a block. The
+ * client returns only whole records, so the requested size does
+ * not need to be rounded to a DIRBLKSIZ boundary.
*/
- left = uio->uio_resid % DIRBLKSIZ;
- if (left == uio->uio_resid)
+ if (uio->uio_offset < 0 ||
+ uio->uio_resid > OFF_MAX - uio->uio_offset)
return (EINVAL);
- uio->uio_resid -= left;
/*
* For readdirplus, if starting to read the directory,
@@ -2670,8 +2668,6 @@
*ap->a_eofflag = 1;
}
- /* Add the partial DIRBLKSIZ (left) back in. */
- uio->uio_resid += left;
return (error);
}

File Metadata

Mime Type
text/plain
Expires
Wed, Oct 14, 9:02 PM (4 h, 26 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40699068
Default Alt Text
D60603.id189506.diff (6 KB)

Event Timeline