+ if (nvap == &nvattr) {
+ NVATTR_CLEANUP(nvap);
+ } else if (!(flags & NGA_ACL)) {
+ /* make sure we don't return an ACL if it wasn't asked for */
+ NFS_BITMAP_CLR(nvap->nva_bitmap, NFS_FATTR_ACL);
+ if (nvap->nva_acl) {
+ kauth_acl_free(nvap->nva_acl);
+ nvap->nva_acl = NULL;
+ }
+ }
+ FSDBG_BOT(513, np->n_size, error, np->n_vattr.nva_size, np->n_flag);
+ return (error);
+}
+
+/*
+ * NFS getattr call from vfs.
+ */
+
+/*
+ * The attributes we support over the wire.
+ * We also get fsid but the vfs layer gets it out of the mount
+ * structure after this calling us so there's no need to return it,
+ * and Finder expects to call getattrlist just looking for the FSID
+ * with out hanging on a non responsive server.
+ */
+#define NFS3_SUPPORTED_VATTRS \
+ (VNODE_ATTR_va_rdev | \
+ VNODE_ATTR_va_nlink | \
+ VNODE_ATTR_va_data_size | \
+ VNODE_ATTR_va_data_alloc | \
+ VNODE_ATTR_va_uid | \
+ VNODE_ATTR_va_gid | \
+ VNODE_ATTR_va_mode | \
+ VNODE_ATTR_va_modify_time | \
+ VNODE_ATTR_va_change_time | \
+ VNODE_ATTR_va_access_time | \
+ VNODE_ATTR_va_fileid | \
+ VNODE_ATTR_va_type)
+
+int
+nfs3_vnop_getattr(
+ struct vnop_getattr_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_vp;
+ struct vnode_attr *a_vap;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ int error;
+ struct nfs_vattr nva;
+ struct vnode_attr *vap = ap->a_vap;
+ dev_t rdev;
+
+ /*
+ * Lets don't go over the wire if we don't support any of the attributes.
+ * Just fall through at the VFS layer and let it cons up what it needs.
+ */
+ /* Return the io size no matter what, since we don't go over the wire for this */
+ VATTR_RETURN(vap, va_iosize, nfs_iosize);
+ if ((vap->va_active & NFS3_SUPPORTED_VATTRS) == 0)
+ return (0);
+
+ if (VATTR_IS_ACTIVE(ap->a_vap, va_name))
+ NFS_VNOP_DBG("Getting attrs for 0x%llx, vname is %s\n",
+ (uint64_t)VM_KERNEL_ADDRPERM(ap->a_vp),
+ ap->a_vp->v_name ? ap->a_vp->v_name : "empty");
+ error = nfs_getattr(VTONFS(ap->a_vp), &nva, ap->a_context, NGA_CACHED);
+ if (error)
+ return (error);
+
+ /* copy nva to *a_vap */
+ VATTR_RETURN(vap, va_type, nva.nva_type);
+ VATTR_RETURN(vap, va_mode, nva.nva_mode);
+ rdev = makedev(nva.nva_rawdev.specdata1, nva.nva_rawdev.specdata2);
+ VATTR_RETURN(vap, va_rdev, rdev);
+ VATTR_RETURN(vap, va_uid, nva.nva_uid);
+ VATTR_RETURN(vap, va_gid, nva.nva_gid);
+ VATTR_RETURN(vap, va_nlink, nva.nva_nlink);
+ VATTR_RETURN(vap, va_fileid, nva.nva_fileid);
+ VATTR_RETURN(vap, va_data_size, nva.nva_size);
+ VATTR_RETURN(vap, va_data_alloc, nva.nva_bytes);
+ vap->va_access_time.tv_sec = nva.nva_timesec[NFSTIME_ACCESS];
+ vap->va_access_time.tv_nsec = nva.nva_timensec[NFSTIME_ACCESS];
+ VATTR_SET_SUPPORTED(vap, va_access_time);
+ vap->va_modify_time.tv_sec = nva.nva_timesec[NFSTIME_MODIFY];
+ vap->va_modify_time.tv_nsec = nva.nva_timensec[NFSTIME_MODIFY];
+ VATTR_SET_SUPPORTED(vap, va_modify_time);
+ vap->va_change_time.tv_sec = nva.nva_timesec[NFSTIME_CHANGE];
+ vap->va_change_time.tv_nsec = nva.nva_timensec[NFSTIME_CHANGE];
+ VATTR_SET_SUPPORTED(vap, va_change_time);
+
+ // VATTR_RETURN(vap, va_encoding, 0xffff /* kTextEncodingUnknown */);
+ return (error);
+}
+
+/*
+ * NFS setattr call.
+ */
+int
+nfs_vnop_setattr(
+ struct vnop_setattr_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_vp;
+ struct vnode_attr *a_vap;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ vfs_context_t ctx = ap->a_context;
+ vnode_t vp = ap->a_vp;
+ nfsnode_t np = VTONFS(vp);
+ struct nfsmount *nmp;
+ struct vnode_attr *vap = ap->a_vap;
+ int error = 0;
+ int biosize, nfsvers, namedattrs;
+ u_quad_t origsize, vapsize;
+ struct nfs_dulookup dul;
+ nfsnode_t dnp = NULL;
+ vnode_t dvp = NULL;
+ const char *vname = NULL;
+ struct nfs_open_owner *noop = NULL;
+ struct nfs_open_file *nofp = NULL;
+
+ nmp = VTONMP(vp);
+ if (nfs_mount_gone(nmp))
+ return (ENXIO);
+ nfsvers = nmp->nm_vers;
+ namedattrs = (nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_NAMED_ATTR);
+ biosize = nmp->nm_biosize;
+
+ /* Disallow write attempts if the filesystem is mounted read-only. */
+ if (vnode_vfsisrdonly(vp))
+ return (EROFS);
+
+ origsize = np->n_size;
+ if (VATTR_IS_ACTIVE(vap, va_data_size)) {
+ switch (vnode_vtype(vp)) {
+ case VDIR:
+ return (EISDIR);
+ case VCHR:
+ case VBLK:
+ case VSOCK:
+ case VFIFO:
+ if (!VATTR_IS_ACTIVE(vap, va_modify_time) &&
+ !VATTR_IS_ACTIVE(vap, va_access_time) &&
+ !VATTR_IS_ACTIVE(vap, va_mode) &&
+ !VATTR_IS_ACTIVE(vap, va_uid) &&
+ !VATTR_IS_ACTIVE(vap, va_gid)) {
+ return (0);
+ }
+ VATTR_CLEAR_ACTIVE(vap, va_data_size);
+ break;
+ default:
+ /*
+ * Disallow write attempts if the filesystem is
+ * mounted read-only.
+ */
+ if (vnode_vfsisrdonly(vp))
+ return (EROFS);
+ FSDBG_TOP(512, np->n_size, vap->va_data_size,
+ np->n_vattr.nva_size, np->n_flag);
+ /* clear NNEEDINVALIDATE, if set */
+ if ((error = nfs_node_lock(np)))
+ return (error);
+ if (np->n_flag & NNEEDINVALIDATE)
+ np->n_flag &= ~NNEEDINVALIDATE;
+ nfs_node_unlock(np);
+ /* flush everything */
+ error = nfs_vinvalbuf(vp, (vap->va_data_size ? V_SAVE : 0) , ctx, 1);
+ if (error) {
+ NP(np, "nfs_setattr: nfs_vinvalbuf %d", error);
+ FSDBG_BOT(512, np->n_size, vap->va_data_size, np->n_vattr.nva_size, -1);
+ return (error);
+ }
+ if (nfsvers >= NFS_VER4) {
+ /* setting file size requires having the file open for write access */
+ if (np->n_flag & NREVOKE)
+ return (EIO);
+ noop = nfs_open_owner_find(nmp, vfs_context_ucred(ctx), 1);
+ if (!noop)
+ return (ENOMEM);
+restart:
+ error = nfs_mount_state_in_use_start(nmp, vfs_context_thread(ctx));
+ if (error)
+ return (error);
+ if (np->n_flag & NREVOKE) {
+ nfs_mount_state_in_use_end(nmp, 0);
+ return (EIO);
+ }
+ error = nfs_open_file_find(np, noop, &nofp, 0, 0, 1);
+ if (!error && (nofp->nof_flags & NFS_OPEN_FILE_LOST))
+ error = EIO;
+ if (!error && (nofp->nof_flags & NFS_OPEN_FILE_REOPEN)) {
+ nfs_mount_state_in_use_end(nmp, 0);
+ error = nfs4_reopen(nofp, vfs_context_thread(ctx));
+ nofp = NULL;
+ if (!error)
+ goto restart;
+ }
+ if (!error)
+ error = nfs_open_file_set_busy(nofp, vfs_context_thread(ctx));
+ if (error) {
+ nfs_open_owner_rele(noop);
+ return (error);
+ }
+ if (!(nofp->nof_access & NFS_OPEN_SHARE_ACCESS_WRITE)) {
+ /* we don't have the file open for write access, so open it */
+ error = nfs4_open(np, nofp, NFS_OPEN_SHARE_ACCESS_WRITE, NFS_OPEN_SHARE_DENY_NONE, ctx);
+ if (!error)
+ nofp->nof_flags |= NFS_OPEN_FILE_SETATTR;
+ if (nfs_mount_state_error_should_restart(error)) {
+ nfs_open_file_clear_busy(nofp);
+ nofp = NULL;
+ if (nfs_mount_state_in_use_end(nmp, error))
+ goto restart;
+ }
+ }
+ }
+ nfs_data_lock(np, NFS_DATA_LOCK_EXCLUSIVE);
+ if (np->n_size > vap->va_data_size) { /* shrinking? */
+ daddr64_t obn, bn;
+ int neweofoff, mustwrite;
+ struct nfsbuf *bp;
+
+ obn = (np->n_size - 1) / biosize;
+ bn = vap->va_data_size / biosize;
+ for ( ; obn >= bn; obn--) {
+ if (!nfs_buf_is_incore(np, obn))
+ continue;
+ error = nfs_buf_get(np, obn, biosize, NULL, NBLK_READ, &bp);
+ if (error)
+ continue;
+ if (obn != bn) {
+ FSDBG(512, bp, bp->nb_flags, 0, obn);
+ SET(bp->nb_flags, NB_INVAL);
+ nfs_buf_release(bp, 1);
+ continue;
+ }
+ mustwrite = 0;
+ neweofoff = vap->va_data_size - NBOFF(bp);
+ /* check for any dirty data before the new EOF */
+ if ((bp->nb_dirtyend > 0) && (bp->nb_dirtyoff < neweofoff)) {
+ /* clip dirty range to EOF */
+ if (bp->nb_dirtyend > neweofoff) {
+ bp->nb_dirtyend = neweofoff;
+ if (bp->nb_dirtyoff >= bp->nb_dirtyend)
+ bp->nb_dirtyoff = bp->nb_dirtyend = 0;
+ }
+ if ((bp->nb_dirtyend > 0) && (bp->nb_dirtyoff < neweofoff))
+ mustwrite++;
+ }
+ bp->nb_dirty &= (1 << round_page_32(neweofoff)/PAGE_SIZE) - 1;
+ if (bp->nb_dirty)
+ mustwrite++;
+ if (!mustwrite) {
+ FSDBG(512, bp, bp->nb_flags, 0, obn);
+ SET(bp->nb_flags, NB_INVAL);
+ nfs_buf_release(bp, 1);
+ continue;
+ }
+ /* gotta write out dirty data before invalidating */
+ /* (NB_STABLE indicates that data writes should be FILESYNC) */
+ /* (NB_NOCACHE indicates buffer should be discarded) */
+ CLR(bp->nb_flags, (NB_DONE | NB_ERROR | NB_INVAL | NB_ASYNC | NB_READ));
+ SET(bp->nb_flags, NB_STABLE | NB_NOCACHE);
+ if (!IS_VALID_CRED(bp->nb_wcred)) {
+ kauth_cred_t cred = vfs_context_ucred(ctx);
+ kauth_cred_ref(cred);
+ bp->nb_wcred = cred;
+ }
+ error = nfs_buf_write(bp);
+ // Note: bp has been released
+ if (error) {
+ FSDBG(512, bp, 0xd00dee, 0xbad, error);
+ nfs_node_lock_force(np);
+ np->n_error = error;
+ np->n_flag |= NWRITEERR;
+ /*
+ * There was a write error and we need to
+ * invalidate attrs and flush buffers in
+ * order to sync up with the server.
+ * (if this write was extending the file,
+ * we may no longer know the correct size)
+ */
+ NATTRINVALIDATE(np);
+ nfs_node_unlock(np);
+ nfs_data_unlock(np);
+ nfs_vinvalbuf(vp, V_SAVE|V_IGNORE_WRITEERR, ctx, 1);
+ nfs_data_lock(np, NFS_DATA_LOCK_EXCLUSIVE);
+ error = 0;
+ }
+ }
+ }
+ if (vap->va_data_size != np->n_size)
+ ubc_setsize(vp, (off_t)vap->va_data_size); /* XXX error? */
+ origsize = np->n_size;
+ np->n_size = np->n_vattr.nva_size = vap->va_data_size;
+ nfs_node_lock_force(np);
+ CLR(np->n_flag, NUPDATESIZE);
+ nfs_node_unlock(np);
+ FSDBG(512, np, np->n_size, np->n_vattr.nva_size, 0xf00d0001);
+ }
+ } else if (VATTR_IS_ACTIVE(vap, va_modify_time) ||
+ VATTR_IS_ACTIVE(vap, va_access_time) ||
+ (vap->va_vaflags & VA_UTIMES_NULL)) {
+ if ((error = nfs_node_lock(np)))
+ return (error);
+ if ((np->n_flag & NMODIFIED) && (vnode_vtype(vp) == VREG)) {
+ nfs_node_unlock(np);
+ error = nfs_vinvalbuf(vp, V_SAVE, ctx, 1);
+ if (error == EINTR)
+ return (error);
+ } else {
+ nfs_node_unlock(np);
+ }
+ }
+ if ((VATTR_IS_ACTIVE(vap, va_mode) || VATTR_IS_ACTIVE(vap, va_uid) || VATTR_IS_ACTIVE(vap, va_gid) ||
+ VATTR_IS_ACTIVE(vap, va_acl) || VATTR_IS_ACTIVE(vap, va_uuuid) || VATTR_IS_ACTIVE(vap, va_guuid)) &&
+ !(error = nfs_node_lock(np))) {
+ NACCESSINVALIDATE(np);
+ nfs_node_unlock(np);
+ if (!namedattrs) {
+ dvp = vnode_getparent(vp);
+ vname = vnode_getname(vp);
+ dnp = (dvp && vname) ? VTONFS(dvp) : NULL;
+ if (dnp) {
+ error = nfs_node_set_busy(dnp, vfs_context_thread(ctx));
+ if (error) {
+ dnp = NULL;
+ error = 0;
+ }
+ }
+ if (dnp) {
+ nfs_dulookup_init(&dul, dnp, vname, strlen(vname), ctx);
+ nfs_dulookup_start(&dul, dnp, ctx);
+ }
+ }
+ }
+
+ if (!error)
+ error = nmp->nm_funcs->nf_setattr_rpc(np, vap, ctx);
+
+ if (VATTR_IS_ACTIVE(vap, va_mode) || VATTR_IS_ACTIVE(vap, va_uid) || VATTR_IS_ACTIVE(vap, va_gid) ||
+ VATTR_IS_ACTIVE(vap, va_acl) || VATTR_IS_ACTIVE(vap, va_uuuid) || VATTR_IS_ACTIVE(vap, va_guuid)) {
+ if (!namedattrs) {
+ if (dnp) {
+ nfs_dulookup_finish(&dul, dnp, ctx);
+ nfs_node_clear_busy(dnp);
+ }
+ if (dvp != NULLVP)
+ vnode_put(dvp);
+ if (vname != NULL)
+ vnode_putname(vname);
+ }
+ }
+
+ FSDBG_BOT(512, np->n_size, vap->va_data_size, np->n_vattr.nva_size, error);
+ if (VATTR_IS_ACTIVE(vap, va_data_size)) {
+ if (error && (origsize != np->n_size) &&
+ ((nfsvers < NFS_VER4) || !nfs_mount_state_error_should_restart(error))) {
+ /* make every effort to resync file size w/ server... */
+ /* (don't bother if we'll be restarting the operation) */
+ int err; /* preserve "error" for return */
+ np->n_size = np->n_vattr.nva_size = origsize;
+ nfs_node_lock_force(np);
+ CLR(np->n_flag, NUPDATESIZE);
+ nfs_node_unlock(np);
+ FSDBG(512, np, np->n_size, np->n_vattr.nva_size, 0xf00d0002);
+ ubc_setsize(vp, (off_t)np->n_size); /* XXX check error */
+ vapsize = vap->va_data_size;
+ vap->va_data_size = origsize;
+ err = nmp->nm_funcs->nf_setattr_rpc(np, vap, ctx);
+ if (err)
+ NP(np, "nfs_vnop_setattr: nfs%d_setattr_rpc %d %d", nfsvers, error, err);
+ vap->va_data_size = vapsize;
+ }
+ nfs_node_lock_force(np);
+ /*
+ * The size was just set. If the size is already marked for update, don't
+ * trust the newsize (it may have been set while the setattr was in progress).
+ * Clear the update flag and make sure we fetch new attributes so we are sure
+ * we have the latest size.
+ */
+ if (ISSET(np->n_flag, NUPDATESIZE)) {
+ CLR(np->n_flag, NUPDATESIZE);
+ NATTRINVALIDATE(np);
+ nfs_node_unlock(np);
+ nfs_getattr(np, NULL, ctx, NGA_UNCACHED);
+ } else {
+ nfs_node_unlock(np);
+ }
+ nfs_data_unlock(np);
+ if (nfsvers >= NFS_VER4) {
+ if (nofp) {
+ /* don't close our setattr open if we'll be restarting... */
+ if (!nfs_mount_state_error_should_restart(error) &&
+ (nofp->nof_flags & NFS_OPEN_FILE_SETATTR)) {
+ int err = nfs_close(np, nofp, NFS_OPEN_SHARE_ACCESS_WRITE, NFS_OPEN_SHARE_DENY_NONE, ctx);
+ if (err)
+ NP(np, "nfs_vnop_setattr: close error: %d", err);
+ nofp->nof_flags &= ~NFS_OPEN_FILE_SETATTR;
+ }
+ nfs_open_file_clear_busy(nofp);
+ nofp = NULL;
+ }
+ if (nfs_mount_state_in_use_end(nmp, error))
+ goto restart;
+ nfs_open_owner_rele(noop);
+ }
+ }
+ return (error);
+}
+
+/*
+ * Do an NFS setattr RPC.
+ */
+int
+nfs3_setattr_rpc(
+ nfsnode_t np,
+ struct vnode_attr *vap,
+ vfs_context_t ctx)
+{
+ struct nfsmount *nmp = NFSTONMP(np);
+ int error = 0, lockerror = ENOENT, status, wccpostattr = 0, nfsvers;
+ u_int64_t xid, nextxid;
+ struct nfsm_chain nmreq, nmrep;
+
+ if (nfs_mount_gone(nmp))
+ return (ENXIO);
+ nfsvers = nmp->nm_vers;
+
+ VATTR_SET_SUPPORTED(vap, va_mode);
+ VATTR_SET_SUPPORTED(vap, va_uid);
+ VATTR_SET_SUPPORTED(vap, va_gid);
+ VATTR_SET_SUPPORTED(vap, va_data_size);
+ VATTR_SET_SUPPORTED(vap, va_access_time);
+ VATTR_SET_SUPPORTED(vap, va_modify_time);
+
+ if (VATTR_IS_ACTIVE(vap, va_flags)) {
+ if (vap->va_flags) { /* we don't support setting flags */
+ if (vap->va_active & ~VNODE_ATTR_va_flags)
+ return (EINVAL); /* return EINVAL if other attributes also set */
+ else
+ return (ENOTSUP); /* return ENOTSUP for chflags(2) */
+ }
+ /* no flags set, so we'll just ignore it */
+ if (!(vap->va_active & ~VNODE_ATTR_va_flags))
+ return (0); /* no (other) attributes to set, so nothing to do */
+ }
+
+ nfsm_chain_null(&nmreq);
+ nfsm_chain_null(&nmrep);
+
+ nfsm_chain_build_alloc_init(error, &nmreq,
+ NFSX_FH(nfsvers) + NFSX_SATTR(nfsvers));
+ nfsm_chain_add_fh(error, &nmreq, nfsvers, np->n_fhp, np->n_fhsize);
+ if (nfsvers == NFS_VER3) {
+ if (VATTR_IS_ACTIVE(vap, va_mode)) {
+ nfsm_chain_add_32(error, &nmreq, TRUE);
+ nfsm_chain_add_32(error, &nmreq, vap->va_mode);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, FALSE);
+ }
+ if (VATTR_IS_ACTIVE(vap, va_uid)) {
+ nfsm_chain_add_32(error, &nmreq, TRUE);
+ nfsm_chain_add_32(error, &nmreq, vap->va_uid);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, FALSE);
+ }
+ if (VATTR_IS_ACTIVE(vap, va_gid)) {
+ nfsm_chain_add_32(error, &nmreq, TRUE);
+ nfsm_chain_add_32(error, &nmreq, vap->va_gid);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, FALSE);
+ }
+ if (VATTR_IS_ACTIVE(vap, va_data_size)) {
+ nfsm_chain_add_32(error, &nmreq, TRUE);
+ nfsm_chain_add_64(error, &nmreq, vap->va_data_size);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, FALSE);
+ }
+ if (vap->va_vaflags & VA_UTIMES_NULL) {
+ nfsm_chain_add_32(error, &nmreq, NFS_TIME_SET_TO_SERVER);
+ nfsm_chain_add_32(error, &nmreq, NFS_TIME_SET_TO_SERVER);
+ } else {
+ if (VATTR_IS_ACTIVE(vap, va_access_time)) {
+ nfsm_chain_add_32(error, &nmreq, NFS_TIME_SET_TO_CLIENT);
+ nfsm_chain_add_32(error, &nmreq, vap->va_access_time.tv_sec);
+ nfsm_chain_add_32(error, &nmreq, vap->va_access_time.tv_nsec);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, NFS_TIME_DONT_CHANGE);
+ }
+ if (VATTR_IS_ACTIVE(vap, va_modify_time)) {
+ nfsm_chain_add_32(error, &nmreq, NFS_TIME_SET_TO_CLIENT);
+ nfsm_chain_add_32(error, &nmreq, vap->va_modify_time.tv_sec);
+ nfsm_chain_add_32(error, &nmreq, vap->va_modify_time.tv_nsec);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, NFS_TIME_DONT_CHANGE);
+ }
+ }
+ nfsm_chain_add_32(error, &nmreq, FALSE);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, VATTR_IS_ACTIVE(vap, va_mode) ?
+ vtonfsv2_mode(vnode_vtype(NFSTOV(np)), vap->va_mode) : -1);
+ nfsm_chain_add_32(error, &nmreq, VATTR_IS_ACTIVE(vap, va_uid) ?
+ vap->va_uid : (uint32_t)-1);
+ nfsm_chain_add_32(error, &nmreq, VATTR_IS_ACTIVE(vap, va_gid) ?
+ vap->va_gid : (uint32_t)-1);
+ nfsm_chain_add_32(error, &nmreq, VATTR_IS_ACTIVE(vap, va_data_size) ?
+ vap->va_data_size : (uint32_t)-1);
+ if (VATTR_IS_ACTIVE(vap, va_access_time)) {
+ nfsm_chain_add_32(error, &nmreq, vap->va_access_time.tv_sec);
+ nfsm_chain_add_32(error, &nmreq, (vap->va_access_time.tv_nsec != -1) ?
+ ((uint32_t)vap->va_access_time.tv_nsec / 1000) : 0xffffffff);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, -1);
+ nfsm_chain_add_32(error, &nmreq, -1);
+ }
+ if (VATTR_IS_ACTIVE(vap, va_modify_time)) {
+ nfsm_chain_add_32(error, &nmreq, vap->va_modify_time.tv_sec);
+ nfsm_chain_add_32(error, &nmreq, (vap->va_modify_time.tv_nsec != -1) ?
+ ((uint32_t)vap->va_modify_time.tv_nsec / 1000) : 0xffffffff);
+ } else {
+ nfsm_chain_add_32(error, &nmreq, -1);
+ nfsm_chain_add_32(error, &nmreq, -1);
+ }
+ }
+ nfsm_chain_build_done(error, &nmreq);
+ nfsmout_if(error);
+ error = nfs_request(np, NULL, &nmreq, NFSPROC_SETATTR, ctx, NULL, &nmrep, &xid, &status);
+ if ((lockerror = nfs_node_lock(np)))
+ error = lockerror;
+ if (nfsvers == NFS_VER3) {
+ struct timespec premtime = { 0, 0 };
+ nfsm_chain_get_wcc_data(error, &nmrep, np, &premtime, &wccpostattr, &xid);
+ nfsmout_if(error);
+ /* if file hadn't changed, update cached mtime */
+ if (nfstimespeccmp(&np->n_mtime, &premtime, ==))
+ NFS_CHANGED_UPDATE(nfsvers, np, &np->n_vattr);
+ /* if directory hadn't changed, update namecache mtime */
+ if ((vnode_vtype(NFSTOV(np)) == VDIR) &&
+ nfstimespeccmp(&np->n_ncmtime, &premtime, ==))
+ NFS_CHANGED_UPDATE_NC(nfsvers, np, &np->n_vattr);
+ if (!wccpostattr)
+ NATTRINVALIDATE(np);
+ error = status;
+ } else {
+ if (!error)
+ error = status;
+ nfsm_chain_loadattr(error, &nmrep, np, nfsvers, &xid);
+ }
+ /*
+ * We just changed the attributes and we want to make sure that we
+ * see the latest attributes. Get the next XID. If it's not the
+ * next XID after the SETATTR XID, then it's possible that another
+ * RPC was in flight at the same time and it might put stale attributes
+ * in the cache. In that case, we invalidate the attributes and set
+ * the attribute cache XID to guarantee that newer attributes will
+ * get loaded next.
+ */
+ nextxid = 0;
+ nfs_get_xid(&nextxid);
+ if (nextxid != (xid + 1)) {
+ np->n_xid = nextxid;
+ NATTRINVALIDATE(np);
+ }
+nfsmout:
+ if (!lockerror)
+ nfs_node_unlock(np);
+ nfsm_chain_cleanup(&nmreq);
+ nfsm_chain_cleanup(&nmrep);
+ return (error);
+}
+
+/*
+ * NFS lookup call, one step at a time...
+ * First look in cache
+ * If not found, unlock the directory nfsnode and do the RPC
+ */
+int
+nfs_vnop_lookup(
+ struct vnop_lookup_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_dvp;
+ vnode_t *a_vpp;
+ struct componentname *a_cnp;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ vfs_context_t ctx = ap->a_context;
+ struct componentname *cnp = ap->a_cnp;
+ vnode_t dvp = ap->a_dvp;
+ vnode_t *vpp = ap->a_vpp;
+ int flags = cnp->cn_flags;
+ vnode_t newvp;
+ nfsnode_t dnp, np;
+ struct nfsmount *nmp;
+ mount_t mp;
+ int nfsvers, error, busyerror = ENOENT, isdot, isdotdot, negnamecache;
+ u_int64_t xid;
+ struct nfs_vattr nvattr;
+ int ngflags;
+ struct vnop_access_args naa;
+ fhandle_t fh;
+ struct nfsreq rq, *req = &rq;
+
+ *vpp = NULLVP;
+
+ dnp = VTONFS(dvp);
+ NVATTR_INIT(&nvattr);
+
+ mp = vnode_mount(dvp);
+ nmp = VFSTONFS(mp);
+ if (nfs_mount_gone(nmp)) {
+ error = ENXIO;
+ goto error_return;
+ }
+ nfsvers = nmp->nm_vers;
+ negnamecache = !NMFLAG(nmp, NONEGNAMECACHE);
+
+ if ((error = busyerror = nfs_node_set_busy(dnp, vfs_context_thread(ctx))))
+ goto error_return;
+ /* nfs_getattr() will check changed and purge caches */
+ if ((error = nfs_getattr(dnp, NULL, ctx, NGA_CACHED)))
+ goto error_return;
+
+ error = cache_lookup(dvp, vpp, cnp);
+ switch (error) {
+ case ENOENT:
+ /* negative cache entry */
+ goto error_return;
+ case 0:
+ /* cache miss */
+ if ((nfsvers > NFS_VER2) && NMFLAG(nmp, RDIRPLUS)) {
+ /* if rdirplus, try dir buf cache lookup */
+ error = nfs_dir_buf_cache_lookup(dnp, &np, cnp, ctx, 0);
+ if (!error && np) {
+ /* dir buf cache hit */
+ *vpp = NFSTOV(np);
+ error = -1;
+ }
+ }
+ if (error != -1) /* cache miss */
+ break;
+ /* FALLTHROUGH */
+ case -1:
+ /* cache hit, not really an error */
+ OSAddAtomic64(1, &nfsstats.lookupcache_hits);
+
+ nfs_node_clear_busy(dnp);
+ busyerror = ENOENT;
+
+ /* check for directory access */
+ naa.a_desc = &vnop_access_desc;
+ naa.a_vp = dvp;
+ naa.a_action = KAUTH_VNODE_SEARCH;
+ naa.a_context = ctx;
+
+ /* compute actual success/failure based on accessibility */
+ error = nfs_vnop_access(&naa);
+ /* FALLTHROUGH */
+ default:
+ /* unexpected error from cache_lookup */
+ goto error_return;
+ }
+
+ /* skip lookup, if we know who we are: "." or ".." */
+ isdot = isdotdot = 0;
+ if (cnp->cn_nameptr[0] == '.') {
+ if (cnp->cn_namelen == 1)
+ isdot = 1;
+ if ((cnp->cn_namelen == 2) && (cnp->cn_nameptr[1] == '.'))
+ isdotdot = 1;
+ }
+ if (isdotdot || isdot) {
+ fh.fh_len = 0;
+ goto found;
+ }
+ if ((nfsvers >= NFS_VER4) && (dnp->n_vattr.nva_flags & NFS_FFLAG_TRIGGER)) {
+ /* we should never be looking things up in a trigger directory, return nothing */
+ error = ENOENT;
+ goto error_return;
+ }
+
+ /* do we know this name is too long? */
+ nmp = VTONMP(dvp);
+ if (nfs_mount_gone(nmp)) {
+ error = ENXIO;
+ goto error_return;
+ }
+ if (NFS_BITMAP_ISSET(nmp->nm_fsattr.nfsa_bitmap, NFS_FATTR_MAXNAME) &&
+ (cnp->cn_namelen > (int)nmp->nm_fsattr.nfsa_maxname)) {
+ error = ENAMETOOLONG;
+ goto error_return;
+ }
+
+ error = 0;
+ newvp = NULLVP;
+
+ OSAddAtomic64(1, &nfsstats.lookupcache_misses);
+
+ error = nmp->nm_funcs->nf_lookup_rpc_async(dnp, cnp->cn_nameptr, cnp->cn_namelen, ctx, &req);
+ nfsmout_if(error);
+ error = nmp->nm_funcs->nf_lookup_rpc_async_finish(dnp, cnp->cn_nameptr, cnp->cn_namelen, ctx, req, &xid, &fh, &nvattr);
+ nfsmout_if(error);
+
+ /* is the file handle the same as this directory's file handle? */
+ isdot = NFS_CMPFH(dnp, fh.fh_data, fh.fh_len);
+
+found:
+ if (flags & ISLASTCN) {
+ switch (cnp->cn_nameiop) {
+ case DELETE:
+ cnp->cn_flags &= ~MAKEENTRY;
+ break;
+ case RENAME:
+ cnp->cn_flags &= ~MAKEENTRY;
+ if (isdot) {
+ error = EISDIR;
+ goto error_return;
+ }
+ break;
+ }
+ }
+
+ if (isdotdot) {
+ newvp = vnode_getparent(dvp);
+ if (!newvp) {
+ error = ENOENT;
+ goto error_return;
+ }
+ } else if (isdot) {
+ error = vnode_get(dvp);
+ if (error)
+ goto error_return;
+ newvp = dvp;
+ nfs_node_lock_force(dnp);
+ if (fh.fh_len && (dnp->n_xid <= xid))
+ nfs_loadattrcache(dnp, &nvattr, &xid, 0);
+ nfs_node_unlock(dnp);
+ } else {
+ ngflags = (cnp->cn_flags & MAKEENTRY) ? NG_MAKEENTRY : 0;
+ error = nfs_nget(mp, dnp, cnp, fh.fh_data, fh.fh_len, &nvattr, &xid, rq.r_auth, ngflags, &np);
+ if (error)
+ goto error_return;
+ newvp = NFSTOV(np);
+ nfs_node_unlock(np);