- p = uio->uio_procp;
- if ((nmp->nm_flag & NFSMNT_NFSV3) &&
- !(nmp->nm_state & NFSSTA_GOTFSINFO))
- (void)nfs_fsinfo(nmp, vp, cred, p);
- biosize = vp->v_mount->mnt_stat.f_iosize;
- /*
- * For nfs, cache consistency can only be maintained approximately.
- * Although RFC1094 does not specify the criteria, the following is
- * believed to be compatible with the reference port.
- * For nqnfs, full cache consistency is maintained within the loop.
- * For nfs:
- * If the file's modify time on the server has changed since the
- * last read rpc or you have written to the file,
- * you may have lost data cache consistency with the
- * server, so flush all of the file's data out of the cache.
- * Then force a getattr rpc to ensure that you have up to date
- * attributes.
- * NB: This implies that cache data can be read when up to
- * NFS_MAXATTRTIMEO seconds out of date. If you find that you need
- * current attributes this could be forced by setting n_xid to 0
- * before the VOP_GETATTR() call.
- */
- if ((nmp->nm_flag & NFSMNT_NQNFS) == 0) {
- if (np->n_flag & NMODIFIED) {
- if (vp->v_type != VREG) {
- if (vp->v_type != VDIR)
- panic("nfs: bioread, not dir");
- nfs_invaldir(vp);
- error = nfs_vinvalbuf(vp, V_SAVE, cred, p, 1);
- if (error) {
- FSDBG_BOT(514, vp, 0xd1e0003, 0, error);
- return (error);
- }
- }
- np->n_xid = 0;
- error = VOP_GETATTR(vp, &vattr, cred, p);
- if (error) {
- FSDBG_BOT(514, vp, 0xd1e0004, 0, error);
- return (error);
- }
- if (vp->v_type == VDIR) {
- /* if directory changed, purge any name cache entries */
- if (np->n_ncmtime != vattr.va_mtime.tv_sec)
- cache_purge(vp);
- np->n_ncmtime = vattr.va_mtime.tv_sec;
+ /* Unlock the buffer. */
+ CLR(bp->nb_lflags, (NBL_BUSY | NBL_WANTED));
+
+ if (need_wakeup)
+ wakeup(bp);
+}
+
+/*
+ * prepare for iterating over an nfsnode's buffer list
+ * this lock protects the queue manipulation
+ * (must be called with nfs_buf_mutex held)
+ */
+int
+nfs_buf_iterprepare(nfsnode_t np, struct nfsbuflists *iterheadp, int flags)
+{
+ struct nfsbuflists *listheadp;
+
+ if (flags & NBI_DIRTY)
+ listheadp = &np->n_dirtyblkhd;
+ else
+ listheadp = &np->n_cleanblkhd;
+
+ if ((flags & NBI_NOWAIT) && (np->n_bufiterflags & NBI_ITER)) {
+ LIST_INIT(iterheadp);
+ return(EWOULDBLOCK);
+ }
+
+ while (np->n_bufiterflags & NBI_ITER) {
+ np->n_bufiterflags |= NBI_ITERWANT;
+ msleep(&np->n_bufiterflags, nfs_buf_mutex, 0, "nfs_buf_iterprepare", NULL);
+ }
+ if (LIST_EMPTY(listheadp)) {
+ LIST_INIT(iterheadp);
+ return(EINVAL);
+ }
+ np->n_bufiterflags |= NBI_ITER;
+
+ iterheadp->lh_first = listheadp->lh_first;
+ listheadp->lh_first->nb_vnbufs.le_prev = &iterheadp->lh_first;
+ LIST_INIT(listheadp);
+
+ return(0);
+}
+
+/*
+ * clean up after iterating over an nfsnode's buffer list
+ * this lock protects the queue manipulation
+ * (must be called with nfs_buf_mutex held)
+ */
+void
+nfs_buf_itercomplete(nfsnode_t np, struct nfsbuflists *iterheadp, int flags)
+{
+ struct nfsbuflists * listheadp;
+ struct nfsbuf *bp;
+
+ if (flags & NBI_DIRTY)
+ listheadp = &np->n_dirtyblkhd;
+ else
+ listheadp = &np->n_cleanblkhd;
+
+ while (!LIST_EMPTY(iterheadp)) {
+ bp = LIST_FIRST(iterheadp);
+ LIST_REMOVE(bp, nb_vnbufs);
+ LIST_INSERT_HEAD(listheadp, bp, nb_vnbufs);
+ }
+
+ np->n_bufiterflags &= ~NBI_ITER;
+ if (np->n_bufiterflags & NBI_ITERWANT) {
+ np->n_bufiterflags &= ~NBI_ITERWANT;
+ wakeup(&np->n_bufiterflags);
+ }
+}
+
+
+/*
+ * Read an NFS buffer for a file.
+ */
+int
+nfs_buf_read(struct nfsbuf *bp)
+{
+ int error = 0;
+ nfsnode_t np;
+ thread_t thd;
+ kauth_cred_t cred;
+
+ np = bp->nb_np;
+ cred = bp->nb_rcred;
+ if (IS_VALID_CRED(cred))
+ kauth_cred_ref(cred);
+ thd = ISSET(bp->nb_flags, NB_ASYNC) ? NULL : current_thread();
+
+ /* sanity checks */
+ if (!ISSET(bp->nb_flags, NB_READ))
+ panic("nfs_buf_read: !NB_READ");
+ if (ISSET(bp->nb_flags, NB_DONE))
+ CLR(bp->nb_flags, NB_DONE);
+
+ NFS_BUF_MAP(bp);
+
+ OSAddAtomic(1, &nfsstats.read_bios);
+
+ error = nfs_buf_read_rpc(bp, thd, cred);
+ /*
+ * For async I/O, the callbacks will finish up the
+ * read. Otherwise, the read has already been finished.
+ */
+
+ if (IS_VALID_CRED(cred))
+ kauth_cred_unref(&cred);
+ return (error);
+}
+
+/*
+ * finish the reading of a buffer
+ */
+void
+nfs_buf_read_finish(struct nfsbuf *bp)
+{
+ nfsnode_t np = bp->nb_np;
+ struct nfsmount *nmp;
+
+ if (!ISSET(bp->nb_flags, NB_ERROR)) {
+ /* update valid range */
+ bp->nb_validoff = 0;
+ bp->nb_validend = bp->nb_endio;
+ if (bp->nb_endio < (int)bp->nb_bufsize) {
+ /*
+ * The read may be short because we have unflushed writes
+ * that are extending the file size and the reads hit the
+ * (old) EOF on the server. So, just make sure nb_validend
+ * correctly tracks EOF.
+ * Note that the missing data should have already been zeroed
+ * in nfs_buf_read_rpc_finish().
+ */
+ off_t boff = NBOFF(bp);
+ if ((off_t)np->n_size >= (boff + bp->nb_bufsize))
+ bp->nb_validend = bp->nb_bufsize;
+ else if ((off_t)np->n_size >= boff)
+ bp->nb_validend = np->n_size - boff;
+ else
+ bp->nb_validend = 0;
+ }
+ if ((nmp = NFSTONMP(np)) && (nmp->nm_vers == NFS_VER2) &&
+ ((NBOFF(bp) + bp->nb_validend) > 0x100000000LL))
+ bp->nb_validend = 0x100000000LL - NBOFF(bp);
+ bp->nb_valid = (1 << (round_page_32(bp->nb_validend) / PAGE_SIZE)) - 1;
+ if (bp->nb_validend & PAGE_MASK) {
+ /* zero-fill remainder of last page */
+ bzero(bp->nb_data + bp->nb_validend, PAGE_SIZE - (bp->nb_validend & PAGE_MASK));
+ }
+ }
+ nfs_buf_iodone(bp);
+}
+
+/*
+ * initiate the NFS READ RPC(s) for a buffer
+ */
+int
+nfs_buf_read_rpc(struct nfsbuf *bp, thread_t thd, kauth_cred_t cred)
+{
+ struct nfsmount *nmp;
+ nfsnode_t np = bp->nb_np;
+ int error = 0, nfsvers, async;
+ int offset, nrpcs;
+ uint32_t nmrsize, length, len;
+ off_t boff;
+ struct nfsreq *req;
+ struct nfsreq_cbinfo cb;
+
+ nmp = NFSTONMP(np);
+ if (!nmp) {
+ bp->nb_error = error = ENXIO;
+ SET(bp->nb_flags, NB_ERROR);
+ nfs_buf_iodone(bp);
+ return (error);
+ }
+ nfsvers = nmp->nm_vers;
+ nmrsize = nmp->nm_rsize;
+
+ boff = NBOFF(bp);
+ offset = 0;
+ length = bp->nb_bufsize;
+
+ if (nfsvers == NFS_VER2) {
+ if (boff > 0xffffffffLL) {
+ bp->nb_error = error = EFBIG;
+ SET(bp->nb_flags, NB_ERROR);
+ nfs_buf_iodone(bp);
+ return (error);
+ }
+ if ((boff + length - 1) > 0xffffffffLL)
+ length = 0x100000000LL - boff;
+ }
+
+ /* Note: Can only do async I/O if nfsiods are configured. */
+ async = (bp->nb_flags & NB_ASYNC);
+ cb.rcb_func = async ? nfs_buf_read_rpc_finish : NULL;
+ cb.rcb_bp = bp;
+
+ bp->nb_offio = bp->nb_endio = 0;
+ bp->nb_rpcs = nrpcs = (length + nmrsize - 1) / nmrsize;
+ if (async && (nrpcs > 1)) {
+ SET(bp->nb_flags, NB_MULTASYNCRPC);
+ } else {
+ CLR(bp->nb_flags, NB_MULTASYNCRPC);
+ }
+
+ while (length > 0) {
+ if (ISSET(bp->nb_flags, NB_ERROR)) {
+ error = bp->nb_error;
+ break;
+ }
+ len = (length > nmrsize) ? nmrsize : length;
+ cb.rcb_args[0] = offset;
+ cb.rcb_args[1] = len;
+ if (nmp->nm_vers >= NFS_VER4)
+ cb.rcb_args[2] = nmp->nm_stategenid;
+ req = NULL;
+ error = nmp->nm_funcs->nf_read_rpc_async(np, boff + offset, len, thd, cred, &cb, &req);
+ if (error)
+ break;
+ offset += len;
+ length -= len;
+ if (async)
+ continue;
+ nfs_buf_read_rpc_finish(req);
+ if (ISSET(bp->nb_flags, NB_ERROR)) {
+ error = bp->nb_error;
+ break;
+ }
+ }
+
+ if (length > 0) {
+ /*
+ * Something bad happened while trying to send the RPC(s).
+ * Wait for any outstanding requests to complete.
+ */
+ bp->nb_error = error;
+ SET(bp->nb_flags, NB_ERROR);
+ if (ISSET(bp->nb_flags, NB_MULTASYNCRPC)) {
+ nrpcs = (length + nmrsize - 1) / nmrsize;
+ lck_mtx_lock(nfs_buf_mutex);
+ bp->nb_rpcs -= nrpcs;
+ if (bp->nb_rpcs == 0) {
+ /* No RPCs left, so the buffer's done */
+ lck_mtx_unlock(nfs_buf_mutex);
+ nfs_buf_iodone(bp);
+ } else {
+ /* wait for the last RPC to mark it done */
+ while (bp->nb_rpcs > 0)
+ msleep(&bp->nb_rpcs, nfs_buf_mutex, 0,
+ "nfs_buf_read_rpc_cancel", NULL);
+ lck_mtx_unlock(nfs_buf_mutex);