+ /* Update size, if necessary */
+ if (ISSET(np->n_flag, NUPDATESIZE))
+ nfs_data_update_size(np, 0);
+
+ error = nfs_node_lock(np);
+ nfsmout_if(error);
+ if (!(flags & (NGA_UNCACHED|NGA_MONITOR)) || ((nfsvers >= NFS_VER4) && (np->n_openflags & N_DELEG_MASK))) {
+ /*
+ * Use the cache or wait for any getattr in progress if:
+ * - it's a cached request, or
+ * - we have a delegation
+ */
+ while (1) {
+ error = nfs_getattrcache(np, nvap, flags);
+ if (!error || (error != ENOENT)) {
+ nfs_node_unlock(np);
+ goto nfsmout;
+ }
+ error = 0;
+ if (!ISSET(np->n_flag, NGETATTRINPROG))
+ break;
+ if (flags & NGA_MONITOR) {
+ /* no need to wait if a request is pending */
+ error = EINPROGRESS;
+ nfs_node_unlock(np);
+ goto nfsmout;
+ }
+ SET(np->n_flag, NGETATTRWANT);
+ msleep(np, &np->n_lock, PZERO-1, "nfsgetattrwant", &ts);
+ if ((error = nfs_sigintr(NFSTONMP(np), NULL, vfs_context_thread(ctx), 0))) {
+ nfs_node_unlock(np);
+ goto nfsmout;
+ }
+ }
+ SET(np->n_flag, NGETATTRINPROG);
+ inprogset = 1;
+ } else if (!ISSET(np->n_flag, NGETATTRINPROG)) {
+ SET(np->n_flag, NGETATTRINPROG);
+ inprogset = 1;
+ } else if (flags & NGA_MONITOR) {
+ /* no need to make a request if one is pending */
+ error = EINPROGRESS;
+ }
+ nfs_node_unlock(np);
+
+ nmp = NFSTONMP(np);
+ if (!nmp)
+ error = ENXIO;
+ if (error)
+ goto nfsmout;
+
+ /*
+ * We might want to try to get both the attributes and access info by
+ * making an ACCESS call and seeing if it returns updated attributes.
+ * But don't bother if we aren't caching access info or if the
+ * attributes returned wouldn't be cached.
+ */
+ if (!(flags & NGA_ACL) && (nfsvers != NFS_VER2) && nfs_access_for_getattr && (nfs_access_cache_timeout > 0)) {
+ if (nfs_attrcachetimeout(np) > 0) {
+ /* OSAddAtomic(1, &nfsstats.accesscache_misses); */
+ u_int32_t access = NFS_ACCESS_ALL;
+ error = nmp->nm_funcs->nf_access_rpc(np, &access, ctx);
+ if (error)
+ goto nfsmout;
+ nfs_node_lock_force(np);
+ error = nfs_getattrcache(np, nvap, flags);
+ nfs_node_unlock(np);
+ if (!error || (error != ENOENT))
+ goto nfsmout;
+ /* Well, that didn't work... just do a getattr... */
+ error = 0;
+ }
+ }
+
+ avoidfloods = 0;
+tryagain:
+ error = nmp->nm_funcs->nf_getattr_rpc(np, NULL, np->n_fhp, np->n_fhsize, flags, ctx, nvap, &xid);
+ if (!error) {
+ nfs_node_lock_force(np);
+ error = nfs_loadattrcache(np, nvap, &xid, 0);
+ nfs_node_unlock(np);
+ }
+ nfsmout_if(error);
+ if (!xid) { /* out-of-order rpc - attributes were dropped */
+ FSDBG(513, -1, np, np->n_xid >> 32, np->n_xid);
+ if (avoidfloods++ < 20)
+ goto tryagain;
+ /* avoidfloods>1 is bizarre. at 20 pull the plug */
+ /* just return the last attributes we got */
+ }
+nfsmout:
+ nfs_node_lock_force(np);
+ if (inprogset) {
+ wanted = ISSET(np->n_flag, NGETATTRWANT);
+ CLR(np->n_flag, (NGETATTRINPROG | NGETATTRWANT));
+ }
+ if (!error) {
+ /* check if the node changed on us */
+ vnode_t vp = NFSTOV(np);
+ enum vtype vtype = vnode_vtype(vp);
+ if ((vtype == VDIR) && NFS_CHANGED_NC(nfsvers, np, nvap)) {
+ FSDBG(513, -1, np, 0, np);
+ np->n_flag &= ~NNEGNCENTRIES;
+ cache_purge(vp);
+ np->n_ncgen++;
+ NFS_CHANGED_UPDATE_NC(nfsvers, np, nvap);
+ NFS_VNOP_DBG("Purge directory 0x%llx\n",
+ (uint64_t)VM_KERNEL_ADDRPERM(vp));
+ }
+ if (NFS_CHANGED(nfsvers, np, nvap)) {
+ FSDBG(513, -1, np, -1, np);
+ if (vtype == VDIR) {
+ NFS_VNOP_DBG("Invalidate directory 0x%llx\n",
+ (uint64_t)VM_KERNEL_ADDRPERM(vp));
+ nfs_invaldir(np);
+ }
+ nfs_node_unlock(np);
+ if (wanted)
+ wakeup(np);
+ error = nfs_vinvalbuf(vp, V_SAVE, ctx, 1);
+ FSDBG(513, -1, np, -2, error);
+ if (!error) {
+ nfs_node_lock_force(np);
+ NFS_CHANGED_UPDATE(nfsvers, np, nvap);
+ nfs_node_unlock(np);
+ }
+ } else {
+ nfs_node_unlock(np);
+ if (wanted)
+ wakeup(np);
+ }
+ } else {
+ nfs_node_unlock(np);
+ if (wanted)
+ wakeup(np);
+ }
+
+ 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 (!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);