+ /* Note: Can only do async I/O if nfsiods are configured. */
+ async = (bp->nb_flags & NB_ASYNC) && (NFSIOD_MAX > 0);
+ bp->nb_commitlevel = NFS_WRITE_FILESYNC;
+ cb.rcb_func = async ? nfs_buf_write_rpc_finish : NULL;
+ cb.rcb_bp = bp;
+
+ if ((nfsvers == NFS_VER2) && ((NBOFF(bp) + bp->nb_endio) > 0xffffffffLL)) {
+ bp->nb_error = error = EFBIG;
+ SET(bp->nb_flags, NB_ERROR);
+ nfs_buf_iodone(bp);
+ return error;
+ }
+
+ auio = uio_createwithbuffer(1, NBOFF(bp) + offset, UIO_SYSSPACE,
+ UIO_WRITE, &uio_buf, sizeof(uio_buf));
+ uio_addiov(auio, CAST_USER_ADDR_T(bp->nb_data + offset), length);
+
+ bp->nb_rpcs = nrpcs = (length + nmwsize - 1) / nmwsize;
+ 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 > nmwsize) ? nmwsize : length;
+ cb.rcb_args[0] = offset;
+ cb.rcb_args[1] = len;
+#if CONFIG_NFS4
+ if (nmp->nm_vers >= NFS_VER4) {
+ cb.rcb_args[2] = nmp->nm_stategenid;
+ }
+#endif
+ if (async && ((error = nfs_async_write_start(nmp)))) {
+ break;
+ }
+ req = NULL;
+ error = nmp->nm_funcs->nf_write_rpc_async(np, auio, len, thd, cred,
+ iomode, &cb, &req);
+ if (error) {
+ if (async) {
+ nfs_async_write_done(nmp);
+ }
+ break;
+ }
+ offset += len;
+ length -= len;
+ if (async) {
+ continue;
+ }
+ nfs_buf_write_rpc_finish(req);
+ }
+
+ if (length > 0) {
+ /*
+ * Something bad happened while trying to send the RPCs.
+ * 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 + nmwsize - 1) / nmwsize;
+ 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_write_finish(bp, thd, cred);
+ } 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_write_rpc_cancel", NULL);
+ }
+ lck_mtx_unlock(nfs_buf_mutex);
+ }
+ } else {
+ nfs_buf_write_finish(bp, thd, cred);
+ }
+ /* It may have just been an interrupt... that's OK */
+ if (!ISSET(bp->nb_flags, NB_ERROR)) {
+ error = 0;
+ }
+ }
+
+ return error;
+}
+
+/*
+ * finish up an NFS WRITE RPC on a buffer
+ */
+void
+nfs_buf_write_rpc_finish(struct nfsreq *req)
+{
+ int error = 0, nfsvers, offset, length, multasyncrpc, finished;
+ int committed = NFS_WRITE_FILESYNC;
+ uint64_t wverf = 0;
+ size_t rlen;
+ void *wakeme = NULL;
+ struct nfsreq_cbinfo cb;
+ struct nfsreq *wreq = NULL;
+ struct nfsbuf *bp;
+ struct nfsmount *nmp;
+ nfsnode_t np;
+ thread_t thd;
+ kauth_cred_t cred;
+ uio_t auio;
+ char uio_buf[UIO_SIZEOF(1)];
+
+finish:
+ np = req->r_np;
+ thd = req->r_thread;
+ cred = req->r_cred;
+ if (IS_VALID_CRED(cred)) {
+ kauth_cred_ref(cred);
+ }
+ cb = req->r_callback;
+ bp = cb.rcb_bp;
+ if (cb.rcb_func) { /* take an extra reference on the nfsreq in case we want to resend it later due to grace error */
+ nfs_request_ref(req, 0);
+ }
+
+ nmp = NFSTONMP(np);
+ if (nfs_mount_gone(nmp)) {
+ SET(bp->nb_flags, NB_ERROR);
+ bp->nb_error = error = ENXIO;
+ }
+ if (error || ISSET(bp->nb_flags, NB_ERROR)) {
+ /* just drop it */
+ nfs_request_async_cancel(req);
+ goto out;
+ }
+ nfsvers = nmp->nm_vers;
+
+ offset = cb.rcb_args[0];
+ rlen = length = cb.rcb_args[1];
+
+ /* finish the RPC */
+ error = nmp->nm_funcs->nf_write_rpc_async_finish(np, req, &committed, &rlen, &wverf);
+ if ((error == EINPROGRESS) && cb.rcb_func) {
+ /* async request restarted */
+ if (cb.rcb_func) {
+ nfs_request_rele(req);
+ }
+ if (IS_VALID_CRED(cred)) {
+ kauth_cred_unref(&cred);
+ }
+ return;
+ }
+#if CONFIG_NFS4
+ if ((nmp->nm_vers >= NFS_VER4) && nfs_mount_state_error_should_restart(error) && !ISSET(bp->nb_flags, NB_ERROR)) {
+ lck_mtx_lock(&nmp->nm_lock);
+ if ((error != NFSERR_OLD_STATEID) && (error != NFSERR_GRACE) && (cb.rcb_args[2] == nmp->nm_stategenid)) {
+ NP(np, "nfs_buf_write_rpc_finish: error %d @ 0x%llx, 0x%x 0x%x, initiating recovery",
+ error, NBOFF(bp) + offset, cb.rcb_args[2], nmp->nm_stategenid);
+ nfs_need_recover(nmp, error);
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ if (np->n_flag & NREVOKE) {
+ error = EIO;
+ } else {
+ if (error == NFSERR_GRACE) {
+ if (cb.rcb_func) {
+ /*
+ * For an async I/O request, handle a grace delay just like
+ * jukebox errors. Set the resend time and queue it up.
+ */
+ struct timeval now;
+ if (req->r_nmrep.nmc_mhead) {
+ mbuf_freem(req->r_nmrep.nmc_mhead);
+ req->r_nmrep.nmc_mhead = NULL;
+ }
+ req->r_error = 0;
+ microuptime(&now);
+ lck_mtx_lock(&req->r_mtx);
+ req->r_resendtime = now.tv_sec + 2;
+ req->r_xid = 0; // get a new XID
+ req->r_flags |= R_RESTART;
+ req->r_start = 0;
+ nfs_asyncio_resend(req);
+ lck_mtx_unlock(&req->r_mtx);
+ if (IS_VALID_CRED(cred)) {
+ kauth_cred_unref(&cred);
+ }
+ /* Note: nfsreq reference taken will be dropped later when finished */
+ return;
+ }
+ /* otherwise, just pause a couple seconds and retry */
+ tsleep(&nmp->nm_state, (PZERO - 1), "nfsgrace", 2 * hz);
+ }
+ if (!(error = nfs_mount_state_wait_for_recovery(nmp))) {
+ rlen = 0;
+ goto writeagain;
+ }
+ }
+ }
+#endif
+ if (error) {
+ SET(bp->nb_flags, NB_ERROR);
+ bp->nb_error = error;
+ }
+ if (error || (nfsvers == NFS_VER2)) {
+ goto out;
+ }
+ if (rlen <= 0) {
+ SET(bp->nb_flags, NB_ERROR);
+ bp->nb_error = error = EIO;
+ goto out;
+ }
+
+ /* save lowest commit level returned */
+ if (committed < bp->nb_commitlevel) {
+ bp->nb_commitlevel = committed;
+ }
+
+ /* check the write verifier */
+ if (!bp->nb_verf) {
+ bp->nb_verf = wverf;
+ } else if (bp->nb_verf != wverf) {
+ /* verifier changed, so buffer will need to be rewritten */
+ bp->nb_flags |= NB_STALEWVERF;
+ bp->nb_commitlevel = NFS_WRITE_UNSTABLE;
+ bp->nb_verf = wverf;
+ }
+
+ /*
+ * check for a short write
+ *
+ * If the server didn't write all the data, then we
+ * need to issue another write for the rest of it.
+ * (Don't bother if the buffer hit an error or stale wverf.)
+ */
+ if (((int)rlen < length) && !(bp->nb_flags & (NB_STALEWVERF | NB_ERROR))) {
+#if CONFIG_NFS4
+writeagain:
+#endif
+ offset += rlen;
+ length -= rlen;
+
+ auio = uio_createwithbuffer(1, NBOFF(bp) + offset, UIO_SYSSPACE,
+ UIO_WRITE, &uio_buf, sizeof(uio_buf));
+ uio_addiov(auio, CAST_USER_ADDR_T(bp->nb_data + offset), length);
+
+ cb.rcb_args[0] = offset;
+ cb.rcb_args[1] = length;
+#if CONFIG_NFS4
+ if (nmp->nm_vers >= NFS_VER4) {
+ cb.rcb_args[2] = nmp->nm_stategenid;
+ }
+#endif
+ // XXX iomode should really match the original request
+ error = nmp->nm_funcs->nf_write_rpc_async(np, auio, length, thd, cred,
+ NFS_WRITE_FILESYNC, &cb, &wreq);
+ if (!error) {
+ if (IS_VALID_CRED(cred)) {
+ kauth_cred_unref(&cred);
+ }
+ if (!cb.rcb_func) {
+ /* if !async we'll need to wait for this RPC to finish */
+ req = wreq;
+ wreq = NULL;
+ goto finish;
+ }
+ nfs_request_rele(req);
+ /*
+ * We're done here.
+ * Outstanding RPC count is unchanged.
+ * Callback will be called when RPC is done.
+ */
+ return;
+ }
+ SET(bp->nb_flags, NB_ERROR);
+ bp->nb_error = error;
+ }
+
+out:
+ if (cb.rcb_func) {
+ nfs_async_write_done(nmp);
+ nfs_request_rele(req);
+ }
+ /*
+ * Decrement outstanding RPC count on buffer
+ * and call nfs_buf_write_finish on last RPC.
+ *
+ * (Note: when there are multiple async RPCs issued for a
+ * buffer we need nfs_buffer_mutex to avoid problems when
+ * aborting a partially-initiated set of RPCs)
+ */
+ multasyncrpc = ISSET(bp->nb_flags, NB_MULTASYNCRPC);
+ if (multasyncrpc) {
+ lck_mtx_lock(nfs_buf_mutex);
+ }
+
+ bp->nb_rpcs--;
+ finished = (bp->nb_rpcs == 0);
+
+ if (multasyncrpc) {
+ lck_mtx_unlock(nfs_buf_mutex);
+ }
+
+ if (finished) {
+ if (multasyncrpc) {
+ wakeme = &bp->nb_rpcs;
+ }
+ nfs_buf_write_finish(bp, thd, cred);
+ if (wakeme) {
+ wakeup(wakeme);
+ }
+ }
+
+ if (IS_VALID_CRED(cred)) {
+ kauth_cred_unref(&cred);
+ }
+}
+
+/*
+ * Send commit(s) for the given node's "needcommit" buffers
+ */
+int
+nfs_flushcommits(nfsnode_t np, int nowait)
+{
+ struct nfsmount *nmp;
+ struct nfsbuf *bp, *prevlbp, *lbp;
+ struct nfsbuflists blist, commitlist;
+ int error = 0, retv, wcred_set, flags, dirty;
+ u_quad_t off, endoff, toff;
+ uint64_t wverf;
+ u_int32_t count;
+ kauth_cred_t wcred = NULL;
+
+ FSDBG_TOP(557, np, 0, 0, 0);
+
+ /*
+ * A nb_flags == (NB_DELWRI | NB_NEEDCOMMIT) block has been written to the
+ * server, but nas not been committed to stable storage on the server
+ * yet. The byte range is worked out for as many nfsbufs as we can handle
+ * and the commit rpc is done.
+ */
+ if (!LIST_EMPTY(&np->n_dirtyblkhd)) {
+ error = nfs_node_lock(np);
+ if (error) {
+ goto done;
+ }
+ np->n_flag |= NMODIFIED;
+ nfs_node_unlock(np);
+ }
+
+ off = (u_quad_t)-1;
+ endoff = 0;
+ wcred_set = 0;
+ LIST_INIT(&commitlist);
+
+ nmp = NFSTONMP(np);
+ if (nfs_mount_gone(nmp)) {
+ error = ENXIO;
+ goto done;
+ }
+ if (nmp->nm_vers == NFS_VER2) {
+ error = EINVAL;
+ goto done;
+ }
+
+ flags = NBI_DIRTY;
+ if (nowait) {
+ flags |= NBI_NOWAIT;
+ }
+ lck_mtx_lock(nfs_buf_mutex);
+ wverf = nmp->nm_verf;
+ if (!nfs_buf_iterprepare(np, &blist, flags)) {
+ while ((bp = LIST_FIRST(&blist))) {
+ LIST_REMOVE(bp, nb_vnbufs);
+ LIST_INSERT_HEAD(&np->n_dirtyblkhd, bp, nb_vnbufs);
+ error = nfs_buf_acquire(bp, NBAC_NOWAIT, 0, 0);
+ if (error) {
+ continue;
+ }
+ if (ISSET(bp->nb_flags, NB_NEEDCOMMIT)) {
+ nfs_buf_check_write_verifier(np, bp);
+ }
+ if (((bp->nb_flags & (NB_DELWRI | NB_NEEDCOMMIT)) != (NB_DELWRI | NB_NEEDCOMMIT)) ||
+ (bp->nb_verf != wverf)) {
+ nfs_buf_drop(bp);
+ continue;
+ }
+ nfs_buf_remfree(bp);
+
+ /* buffer UPLs will be grabbed *in order* below */
+
+ FSDBG(557, bp, bp->nb_flags, bp->nb_valid, bp->nb_dirty);
+ FSDBG(557, bp->nb_validoff, bp->nb_validend,
+ bp->nb_dirtyoff, bp->nb_dirtyend);
+
+ /*
+ * Work out if all buffers are using the same cred
+ * so we can deal with them all with one commit.
+ *
+ * Note: creds in bp's must be obtained by kauth_cred_ref
+ * on the same original cred in order for them to be equal.
+ */
+ if (wcred_set == 0) {
+ wcred = bp->nb_wcred;
+ if (!IS_VALID_CRED(wcred)) {
+ panic("nfs: needcommit w/out wcred");
+ }
+ wcred_set = 1;
+ } else if ((wcred_set == 1) && wcred != bp->nb_wcred) {
+ wcred_set = -1;
+ }
+ SET(bp->nb_flags, NB_WRITEINPROG);
+
+ /*
+ * Add this buffer to the list of buffers we are committing.
+ * Buffers are inserted into the list in ascending order so that
+ * we can take the UPLs in order after the list is complete.
+ */
+ prevlbp = NULL;
+ LIST_FOREACH(lbp, &commitlist, nb_vnbufs) {
+ if (bp->nb_lblkno < lbp->nb_lblkno) {
+ break;
+ }
+ prevlbp = lbp;
+ }
+ LIST_REMOVE(bp, nb_vnbufs);
+ if (prevlbp) {
+ LIST_INSERT_AFTER(prevlbp, bp, nb_vnbufs);
+ } else {
+ LIST_INSERT_HEAD(&commitlist, bp, nb_vnbufs);
+ }
+
+ /* update commit range start, end */
+ toff = NBOFF(bp) + bp->nb_dirtyoff;
+ if (toff < off) {
+ off = toff;
+ }
+ toff += (u_quad_t)(bp->nb_dirtyend - bp->nb_dirtyoff);
+ if (toff > endoff) {
+ endoff = toff;
+ }
+ }
+ nfs_buf_itercomplete(np, &blist, NBI_DIRTY);
+ }
+ lck_mtx_unlock(nfs_buf_mutex);
+
+ if (LIST_EMPTY(&commitlist)) {
+ error = ENOBUFS;
+ goto done;
+ }
+
+ /*
+ * We need a UPL to prevent others from accessing the buffers during
+ * our commit RPC(s).
+ *
+ * We used to also check for dirty pages here; if there were any we'd
+ * abort the commit and force the entire buffer to be written again.
+ * Instead of doing that, we just go ahead and commit the dirty range,
+ * and then leave the buffer around with dirty pages that will be
+ * written out later.
+ */
+ LIST_FOREACH(bp, &commitlist, nb_vnbufs) {
+ if (!ISSET(bp->nb_flags, NB_PAGELIST)) {
+ retv = nfs_buf_upl_setup(bp);
+ if (retv) {
+ /* Unable to create the UPL, the VM object probably no longer exists. */
+ printf("nfs_flushcommits: upl create failed %d\n", retv);
+ bp->nb_valid = bp->nb_dirty = 0;
+ }
+ }
+ nfs_buf_upl_check(bp);
+ }
+
+ /*
+ * Commit data on the server, as required.
+ * If all bufs are using the same wcred, then use that with
+ * one call for all of them, otherwise commit each one
+ * separately.
+ */
+ if (wcred_set == 1) {
+ /*
+ * Note, it's possible the commit range could be >2^32-1.
+ * If it is, we'll send one commit that covers the whole file.
+ */
+ if ((endoff - off) > 0xffffffff) {
+ count = 0;
+ } else {
+ count = (endoff - off);
+ }
+ retv = nmp->nm_funcs->nf_commit_rpc(np, off, count, wcred, wverf);
+ } else {
+ retv = 0;
+ LIST_FOREACH(bp, &commitlist, nb_vnbufs) {
+ toff = NBOFF(bp) + bp->nb_dirtyoff;
+ count = bp->nb_dirtyend - bp->nb_dirtyoff;
+ retv = nmp->nm_funcs->nf_commit_rpc(np, toff, count, bp->nb_wcred, wverf);
+ if (retv) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * Now, either mark the blocks I/O done or mark the
+ * blocks dirty, depending on whether the commit
+ * succeeded.
+ */
+ while ((bp = LIST_FIRST(&commitlist))) {
+ LIST_REMOVE(bp, nb_vnbufs);
+ FSDBG(557, bp, retv, bp->nb_flags, bp->nb_dirty);
+ nfs_node_lock_force(np);
+ CLR(bp->nb_flags, (NB_NEEDCOMMIT | NB_WRITEINPROG));
+ np->n_needcommitcnt--;
+ CHECK_NEEDCOMMITCNT(np);
+ nfs_node_unlock(np);
+
+ if (retv) {
+ /* move back to dirty list */
+ lck_mtx_lock(nfs_buf_mutex);
+ LIST_INSERT_HEAD(&np->n_dirtyblkhd, bp, nb_vnbufs);
+ lck_mtx_unlock(nfs_buf_mutex);
+ nfs_buf_release(bp, 1);
+ continue;
+ }
+
+ nfs_node_lock_force(np);
+ np->n_numoutput++;
+ nfs_node_unlock(np);
+ vnode_startwrite(NFSTOV(np));
+ if (ISSET(bp->nb_flags, NB_DELWRI)) {
+ lck_mtx_lock(nfs_buf_mutex);
+ nfs_nbdwrite--;
+ NFSBUFCNTCHK();
+ lck_mtx_unlock(nfs_buf_mutex);
+ wakeup(&nfs_nbdwrite);
+ }
+ CLR(bp->nb_flags, (NB_READ | NB_DONE | NB_ERROR | NB_DELWRI));
+ /* if block still has dirty pages, we don't want it to */
+ /* be released in nfs_buf_iodone(). So, don't set NB_ASYNC. */
+ if (!(dirty = bp->nb_dirty)) {
+ SET(bp->nb_flags, NB_ASYNC);
+ } else {
+ CLR(bp->nb_flags, NB_ASYNC);
+ }
+
+ /* move to clean list */
+ lck_mtx_lock(nfs_buf_mutex);
+ LIST_INSERT_HEAD(&np->n_cleanblkhd, bp, nb_vnbufs);
+ lck_mtx_unlock(nfs_buf_mutex);
+
+ bp->nb_dirtyoff = bp->nb_dirtyend = 0;
+
+ nfs_buf_iodone(bp);
+ if (dirty) {
+ /* throw it back in as a delayed write buffer */
+ CLR(bp->nb_flags, NB_DONE);
+ nfs_buf_write_delayed(bp);
+ }
+ }
+
+done:
+ FSDBG_BOT(557, np, 0, 0, error);
+ return error;
+}
+
+/*
+ * Flush all the blocks associated with a vnode.
+ * Walk through the buffer pool and push any dirty pages
+ * associated with the vnode.
+ */
+int
+nfs_flush(nfsnode_t np, int waitfor, thread_t thd, int ignore_writeerr)
+{
+ struct nfsbuf *bp;
+ struct nfsbuflists blist;
+ struct nfsmount *nmp = NFSTONMP(np);
+ int error = 0, error2, slptimeo = 0, slpflag = 0;
+ int nfsvers, flags, passone = 1;
+
+ FSDBG_TOP(517, np, waitfor, ignore_writeerr, 0);
+
+ if (nfs_mount_gone(nmp)) {
+ error = ENXIO;
+ goto out;
+ }
+ nfsvers = nmp->nm_vers;
+ if (NMFLAG(nmp, INTR)) {
+ slpflag = PCATCH;
+ }
+
+ if (!LIST_EMPTY(&np->n_dirtyblkhd)) {
+ nfs_node_lock_force(np);
+ np->n_flag |= NMODIFIED;
+ nfs_node_unlock(np);
+ }
+
+ lck_mtx_lock(nfs_buf_mutex);
+ while (np->n_bflag & NBFLUSHINPROG) {
+ np->n_bflag |= NBFLUSHWANT;
+ error = msleep(&np->n_bflag, nfs_buf_mutex, slpflag, "nfs_flush", NULL);
+ if ((error && (error != EWOULDBLOCK)) ||
+ ((error = nfs_sigintr(NFSTONMP(np), NULL, thd, 0)))) {
+ lck_mtx_unlock(nfs_buf_mutex);
+ goto out;
+ }
+ }
+ np->n_bflag |= NBFLUSHINPROG;
+
+ /*
+ * On the first pass, start async/unstable writes on all
+ * delayed write buffers. Then wait for all writes to complete
+ * and call nfs_flushcommits() to commit any uncommitted buffers.
+ * On all subsequent passes, start STABLE writes on any remaining
+ * dirty buffers. Then wait for all writes to complete.
+ */
+again:
+ FSDBG(518, LIST_FIRST(&np->n_dirtyblkhd), np->n_flag, 0, 0);
+ if (!NFSTONMP(np)) {
+ lck_mtx_unlock(nfs_buf_mutex);
+ error = ENXIO;
+ goto done;
+ }
+
+ /* Start/do any write(s) that are required. */
+ if (!nfs_buf_iterprepare(np, &blist, NBI_DIRTY)) {
+ while ((bp = LIST_FIRST(&blist))) {
+ LIST_REMOVE(bp, nb_vnbufs);
+ LIST_INSERT_HEAD(&np->n_dirtyblkhd, bp, nb_vnbufs);
+ flags = (passone || !(waitfor == MNT_WAIT || waitfor == MNT_DWAIT)) ? NBAC_NOWAIT : 0;
+ if (flags != NBAC_NOWAIT) {
+ nfs_buf_refget(bp);
+ }
+ while ((error = nfs_buf_acquire(bp, flags, slpflag, slptimeo))) {
+ FSDBG(524, bp, flags, bp->nb_lflags, bp->nb_flags);
+ if (error == EBUSY) {
+ break;
+ }
+ if (error) {
+ error2 = nfs_sigintr(NFSTONMP(np), NULL, thd, 0);
+ if (error2) {
+ if (flags != NBAC_NOWAIT) {
+ nfs_buf_refrele(bp);
+ }
+ nfs_buf_itercomplete(np, &blist, NBI_DIRTY);
+ lck_mtx_unlock(nfs_buf_mutex);
+ error = error2;
+ goto done;
+ }
+ if (slpflag == PCATCH) {
+ slpflag = 0;
+ slptimeo = 2 * hz;
+ }
+ }
+ }
+ if (flags != NBAC_NOWAIT) {
+ nfs_buf_refrele(bp);
+ }
+ if (error == EBUSY) {
+ continue;
+ }
+ if (!bp->nb_np) {
+ /* buffer is no longer valid */
+ nfs_buf_drop(bp);
+ continue;
+ }
+ if (ISSET(bp->nb_flags, NB_NEEDCOMMIT)) {
+ nfs_buf_check_write_verifier(np, bp);
+ }
+ if (!ISSET(bp->nb_flags, NB_DELWRI)) {
+ /* buffer is no longer dirty */
+ nfs_buf_drop(bp);
+ continue;
+ }
+ FSDBG(525, bp, passone, bp->nb_lflags, bp->nb_flags);
+ if ((passone || !(waitfor == MNT_WAIT || waitfor == MNT_DWAIT)) &&
+ ISSET(bp->nb_flags, NB_NEEDCOMMIT)) {
+ nfs_buf_drop(bp);
+ continue;
+ }
+ nfs_buf_remfree(bp);
+ lck_mtx_unlock(nfs_buf_mutex);
+ if (ISSET(bp->nb_flags, NB_ERROR)) {
+ nfs_node_lock_force(np);
+ np->n_error = bp->nb_error ? bp->nb_error : EIO;
+ np->n_flag |= NWRITEERR;
+ nfs_node_unlock(np);
+ nfs_buf_release(bp, 1);
+ lck_mtx_lock(nfs_buf_mutex);
+ continue;
+ }
+ SET(bp->nb_flags, NB_ASYNC);
+ if (!passone) {
+ /* NB_STABLE forces this to be written FILESYNC */
+ SET(bp->nb_flags, NB_STABLE);
+ }
+ nfs_buf_write(bp);
+ lck_mtx_lock(nfs_buf_mutex);
+ }
+ nfs_buf_itercomplete(np, &blist, NBI_DIRTY);
+ }
+ lck_mtx_unlock(nfs_buf_mutex);
+
+ if (waitfor == MNT_WAIT || waitfor == MNT_DWAIT) {
+ while ((error = vnode_waitforwrites(NFSTOV(np), 0, slpflag, slptimeo, "nfsflush"))) {
+ error2 = nfs_sigintr(NFSTONMP(np), NULL, thd, 0);
+ if (error2) {
+ error = error2;
+ goto done;
+ }
+ if (slpflag == PCATCH) {
+ slpflag = 0;
+ slptimeo = 2 * hz;
+ }
+ }
+ }
+
+ if (nfsvers != NFS_VER2) {
+ /* loop while it looks like there are still buffers to be */
+ /* commited and nfs_flushcommits() seems to be handling them. */
+ while (np->n_needcommitcnt) {
+ if (nfs_flushcommits(np, 0)) {
+ break;
+ }
+ }
+ }
+
+ if (passone) {
+ passone = 0;
+ if (!LIST_EMPTY(&np->n_dirtyblkhd)) {
+ nfs_node_lock_force(np);
+ np->n_flag |= NMODIFIED;
+ nfs_node_unlock(np);
+ }
+ lck_mtx_lock(nfs_buf_mutex);
+ goto again;
+ }
+
+ if (waitfor == MNT_WAIT || waitfor == MNT_DWAIT) {
+ if (!LIST_EMPTY(&np->n_dirtyblkhd)) {
+ nfs_node_lock_force(np);
+ np->n_flag |= NMODIFIED;
+ nfs_node_unlock(np);
+ }
+ lck_mtx_lock(nfs_buf_mutex);
+ if (!LIST_EMPTY(&np->n_dirtyblkhd)) {
+ goto again;