+
+ if (vtype != VREG) {
+ /* Just mark that it was closed */
+ lck_mtx_lock(&np->n_openlock);
+ if (np->n_openrefcnt == 0) {
+ if (fflag & (FREAD | FWRITE)) {
+ NP(np, "nfs_vnop_close: open reference underrun");
+ error = EINVAL;
+ }
+ } else if (fflag & (FREAD | FWRITE)) {
+ np->n_openrefcnt--;
+ } else {
+ /* No FREAD/FWRITE set - probably the final close */
+ np->n_openrefcnt = 0;
+ }
+ lck_mtx_unlock(&np->n_openlock);
+ return error;
+ }
+ error1 = error;
+
+ /* fflag should contain some combination of: FREAD, FWRITE */
+ accessMode = 0;
+ if (fflag & FREAD) {
+ accessMode |= NFS_OPEN_SHARE_ACCESS_READ;
+ }
+ if (fflag & FWRITE) {
+ accessMode |= NFS_OPEN_SHARE_ACCESS_WRITE;
+ }
+// XXX It would be nice if we still had the O_EXLOCK/O_SHLOCK flags that were on the open
+// if (fflag & O_EXLOCK)
+// denyMode = NFS_OPEN_SHARE_DENY_BOTH;
+// else if (fflag & O_SHLOCK)
+// denyMode = NFS_OPEN_SHARE_DENY_WRITE;
+// else
+// denyMode = NFS_OPEN_SHARE_DENY_NONE;
+ // XXX don't do deny modes just yet (and never do it for !v4)
+ denyMode = NFS_OPEN_SHARE_DENY_NONE;
+
+ if (!accessMode) {
+ /*
+ * No mode given to close?
+ * Guess this is the final close.
+ * We should unlock all locks and close all opens.
+ */
+ uint32_t writers;
+ mount_t mp = vnode_mount(vp);
+ int force = (!mp || vfs_isforce(mp));
+
+ writers = nfs_no_of_open_file_writers(np);
+ nfs_release_open_state_for_node(np, force);
+ if (writers) {
+ lck_mtx_lock(&nmp->nm_lock);
+ if (writers > nmp->nm_writers) {
+ NP(np, "nfs_vnop_close: number of write opens for mount underrun. Node has %d"
+ " opens for write. Mount has total of %d opens for write\n",
+ writers, nmp->nm_writers);
+ nmp->nm_writers = 0;
+ } else {
+ nmp->nm_writers -= writers;
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ }
+
+ return error;
+ } else if (fflag & FWRITE) {
+ lck_mtx_lock(&nmp->nm_lock);
+ if (nmp->nm_writers == 0) {
+ NP(np, "nfs_vnop_close: removing open writer from mount, but mount has no files open for writing");
+ } else {
+ nmp->nm_writers--;
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ }
+
+
+ noop = nfs_open_owner_find(nmp, vfs_context_ucred(ctx), 0);
+ if (!noop) {
+ // printf("nfs_vnop_close: can't get open owner!\n");
+ return EIO;
+ }
+
+restart:
+ error = nfs_mount_state_in_use_start(nmp, NULL);
+ if (error) {
+ nfs_open_owner_rele(noop);
+ return error;
+ }
+
+ error = nfs_open_file_find(np, noop, &nofp, 0, 0, 0);
+#if CONFIG_NFS4
+ if (!error && (nofp->nof_flags & NFS_OPEN_FILE_REOPEN)) {
+ error = nfs4_reopen(nofp, NULL);
+ nofp = NULL;
+ if (!error) {
+ nfs_mount_state_in_use_end(nmp, 0);
+ goto restart;
+ }
+ }
+#endif
+ if (error) {
+ NP(np, "nfs_vnop_close: no open file for owner, error %d, %d", error, kauth_cred_getuid(noop->noo_cred));
+ error = EBADF;
+ goto out;
+ }
+ error = nfs_open_file_set_busy(nofp, NULL);
+ if (error) {
+ nofp = NULL;
+ goto out;
+ }
+
+ error = nfs_close(np, nofp, accessMode, denyMode, ctx);
+ if (error) {
+ NP(np, "nfs_vnop_close: close error %d, %d", error, kauth_cred_getuid(noop->noo_cred));
+ }
+
+out:
+ if (nofp) {
+ nfs_open_file_clear_busy(nofp);
+ }
+ if (nfs_mount_state_in_use_end(nmp, error)) {
+ nofp = NULL;
+ goto restart;
+ }
+ if (!error) {
+ error = error1;
+ }
+ if (error) {
+ NP(np, "nfs_vnop_close: error %d, %d", error, kauth_cred_getuid(noop->noo_cred));
+ }
+ if (noop) {
+ nfs_open_owner_rele(noop);
+ }
+ return error;
+}
+
+/*
+ * nfs_close(): common function that does all the heavy lifting of file closure
+ *
+ * Takes an open file structure and a set of access/deny modes and figures out how
+ * to update the open file structure (and the state on the server) appropriately.
+ */
+int
+nfs_close(
+ nfsnode_t np,
+ struct nfs_open_file *nofp,
+ uint32_t accessMode,
+ uint32_t denyMode,
+ __unused vfs_context_t ctx)
+{
+#if CONFIG_NFS4
+ struct nfs_lock_owner *nlop;
+#endif
+ int error = 0, changed = 0, delegated = 0, closed = 0, downgrade = 0;
+ uint8_t newAccessMode, newDenyMode;
+
+ /* warn if modes don't match current state */
+ if (((accessMode & nofp->nof_access) != accessMode) || ((denyMode & nofp->nof_deny) != denyMode)) {
+ NP(np, "nfs_close: mode mismatch %d %d, current %d %d, %d",
+ accessMode, denyMode, nofp->nof_access, nofp->nof_deny,
+ kauth_cred_getuid(nofp->nof_owner->noo_cred));
+ }
+
+ /*
+ * If we're closing a write-only open, we may not have a write-only count
+ * if we also grabbed read access. So, check the read-write count.
+ */
+ if (denyMode == NFS_OPEN_SHARE_DENY_NONE) {
+ if ((accessMode == NFS_OPEN_SHARE_ACCESS_WRITE) &&
+ (nofp->nof_w == 0) && (nofp->nof_d_w == 0) &&
+ (nofp->nof_rw || nofp->nof_d_rw)) {
+ accessMode = NFS_OPEN_SHARE_ACCESS_BOTH;
+ }
+ } else if (denyMode == NFS_OPEN_SHARE_DENY_WRITE) {
+ if ((accessMode == NFS_OPEN_SHARE_ACCESS_WRITE) &&
+ (nofp->nof_w_dw == 0) && (nofp->nof_d_w_dw == 0) &&
+ (nofp->nof_rw_dw || nofp->nof_d_rw_dw)) {
+ accessMode = NFS_OPEN_SHARE_ACCESS_BOTH;
+ }
+ } else { /* NFS_OPEN_SHARE_DENY_BOTH */
+ if ((accessMode == NFS_OPEN_SHARE_ACCESS_WRITE) &&
+ (nofp->nof_w_drw == 0) && (nofp->nof_d_w_drw == 0) &&
+ (nofp->nof_rw_drw || nofp->nof_d_rw_drw)) {
+ accessMode = NFS_OPEN_SHARE_ACCESS_BOTH;
+ }
+ }
+
+ nfs_open_file_remove_open_find(nofp, accessMode, denyMode, &newAccessMode, &newDenyMode, &delegated);
+ if ((newAccessMode != nofp->nof_access) || (newDenyMode != nofp->nof_deny)) {
+ changed = 1;
+ } else {
+ changed = 0;
+ }
+
+ if (NFSTONMP(np)->nm_vers < NFS_VER4) {
+ /* NFS v2/v3 closes simply need to remove the open. */
+ goto v3close;
+ }
+#if CONFIG_NFS4
+ if ((newAccessMode == 0) || (nofp->nof_opencnt == 1)) {
+ /*
+ * No more access after this close, so clean up and close it.
+ * Don't send a close RPC if we're closing a delegated open.
+ */
+ nfs_wait_bufs(np);
+ closed = 1;
+ if (!delegated && !(nofp->nof_flags & NFS_OPEN_FILE_LOST)) {
+ error = nfs4_close_rpc(np, nofp, vfs_context_thread(ctx), vfs_context_ucred(ctx), 0);
+ }
+ if (error == NFSERR_LOCKS_HELD) {
+ /*
+ * Hmm... the server says we have locks we need to release first
+ * Find the lock owner and try to unlock everything.
+ */
+ nlop = nfs_lock_owner_find(np, vfs_context_proc(ctx), 0);
+ if (nlop) {
+ nfs4_unlock_rpc(np, nlop, F_WRLCK, 0, UINT64_MAX,
+ 0, vfs_context_thread(ctx), vfs_context_ucred(ctx));
+ nfs_lock_owner_rele(nlop);
+ }
+ error = nfs4_close_rpc(np, nofp, vfs_context_thread(ctx), vfs_context_ucred(ctx), 0);
+ }
+ } else if (changed) {
+ /*
+ * File is still open but with less access, so downgrade the open.
+ * Don't send a downgrade RPC if we're closing a delegated open.
+ */
+ if (!delegated && !(nofp->nof_flags & NFS_OPEN_FILE_LOST)) {
+ downgrade = 1;
+ /*
+ * If we have delegated opens, we should probably claim them before sending
+ * the downgrade because the server may not know the open we are downgrading to.
+ */
+ if (nofp->nof_d_rw_drw || nofp->nof_d_w_drw || nofp->nof_d_r_drw ||
+ nofp->nof_d_rw_dw || nofp->nof_d_w_dw || nofp->nof_d_r_dw ||
+ nofp->nof_d_rw || nofp->nof_d_w || nofp->nof_d_r) {
+ nfs4_claim_delegated_state_for_open_file(nofp, 0);
+ }
+ /* need to remove the open before sending the downgrade */
+ nfs_open_file_remove_open(nofp, accessMode, denyMode);
+ error = nfs4_open_downgrade_rpc(np, nofp, ctx);
+ if (error) { /* Hmm.. that didn't work. Add the open back in. */
+ nfs_open_file_add_open(nofp, accessMode, denyMode, delegated);
+ }
+ }
+ }
+#endif
+v3close:
+ if (error) {
+ NP(np, "nfs_close: error %d, %d", error, kauth_cred_getuid(nofp->nof_owner->noo_cred));
+ return error;
+ }
+
+ if (!downgrade) {
+ nfs_open_file_remove_open(nofp, accessMode, denyMode);
+ }
+
+ if (closed) {
+ lck_mtx_lock(&nofp->nof_lock);
+ if (nofp->nof_r || nofp->nof_d_r || nofp->nof_w || nofp->nof_d_w || nofp->nof_d_rw ||
+ (nofp->nof_rw && !((nofp->nof_flags & NFS_OPEN_FILE_CREATE) && !nofp->nof_creator && (nofp->nof_rw == 1))) ||
+ nofp->nof_r_dw || nofp->nof_d_r_dw || nofp->nof_w_dw || nofp->nof_d_w_dw ||
+ nofp->nof_rw_dw || nofp->nof_d_rw_dw || nofp->nof_r_drw || nofp->nof_d_r_drw ||
+ nofp->nof_w_drw || nofp->nof_d_w_drw || nofp->nof_rw_drw || nofp->nof_d_rw_drw) {
+ NP(np, "nfs_close: unexpected count: %u.%u %u.%u %u.%u dw %u.%u %u.%u %u.%u drw %u.%u %u.%u %u.%u flags 0x%x, %d",
+ nofp->nof_r, nofp->nof_d_r, nofp->nof_w, nofp->nof_d_w,
+ nofp->nof_rw, nofp->nof_d_rw, nofp->nof_r_dw, nofp->nof_d_r_dw,
+ nofp->nof_w_dw, nofp->nof_d_w_dw, nofp->nof_rw_dw, nofp->nof_d_rw_dw,
+ nofp->nof_r_drw, nofp->nof_d_r_drw, nofp->nof_w_drw, nofp->nof_d_w_drw,
+ nofp->nof_rw_drw, nofp->nof_d_rw_drw, nofp->nof_flags,
+ kauth_cred_getuid(nofp->nof_owner->noo_cred));
+ }
+ /* clear out all open info, just to be safe */
+ nofp->nof_access = nofp->nof_deny = 0;
+ nofp->nof_mmap_access = nofp->nof_mmap_deny = 0;
+ nofp->nof_r = nofp->nof_d_r = 0;
+ nofp->nof_w = nofp->nof_d_w = 0;
+ nofp->nof_rw = nofp->nof_d_rw = 0;
+ nofp->nof_r_dw = nofp->nof_d_r_dw = 0;
+ nofp->nof_w_dw = nofp->nof_d_w_dw = 0;
+ nofp->nof_rw_dw = nofp->nof_d_rw_dw = 0;
+ nofp->nof_r_drw = nofp->nof_d_r_drw = 0;
+ nofp->nof_w_drw = nofp->nof_d_w_drw = 0;
+ nofp->nof_rw_drw = nofp->nof_d_rw_drw = 0;
+ nofp->nof_flags &= ~NFS_OPEN_FILE_CREATE;
+ lck_mtx_unlock(&nofp->nof_lock);
+ /* XXX we may potentially want to clean up idle/unused open file structures */
+ }
+ if (nofp->nof_flags & NFS_OPEN_FILE_LOST) {
+ error = EIO;
+ NP(np, "nfs_close: LOST%s, %d", !nofp->nof_opencnt ? " (last)" : "",
+ kauth_cred_getuid(nofp->nof_owner->noo_cred));
+ }
+
+ return error;