+ error = 0;
+ preattrerr = postattrerr = ENOENT;
+ nfsm_chain_null(&nmrep);
+ vp = NULL;
+
+ *mrepp = NULL;
+ if (*ndp) {
+ nd = *ndp;
+ *ndp = NULL;
+ nmreq = &nd->nd_nmreq;
+ LIST_INIT(&nd->nd_coalesce);
+ nd->nd_mrep = NULL;
+ nd->nd_stable = NFS_WRITE_FILESYNC;
+ microuptime(&now);
+ cur_usec = (u_quad_t)now.tv_sec * 1000000 + (u_quad_t)now.tv_usec;
+ nd->nd_time = cur_usec +
+ ((nd->nd_vers == NFS_VER3) ? nfsrv_wg_delay_v3 : nfsrv_wg_delay);
+
+ /* Now, get the write header... */
+ nfsm_chain_get_fh_ptr(error, nmreq, nd->nd_vers, nd->nd_fh.nfh_fhp, nd->nd_fh.nfh_len);
+ /* XXX shouldn't we be checking for invalid FHs before doing any more work? */
+ nfsmerr_if(error);
+ if (nd->nd_vers == NFS_VER3) {
+ nfsm_chain_get_64(error, nmreq, nd->nd_off);
+ nfsm_chain_adv(error, nmreq, NFSX_UNSIGNED);
+ nfsm_chain_get_32(error, nmreq, nd->nd_stable);
+ } else {
+ nfsm_chain_adv(error, nmreq, NFSX_UNSIGNED);
+ nfsm_chain_get_32(error, nmreq, nd->nd_off);
+ nfsm_chain_adv(error, nmreq, NFSX_UNSIGNED);
+ if (nfsrv_async) {
+ nd->nd_stable = NFS_WRITE_UNSTABLE;
+ }
+ }
+ nfsm_chain_get_32(error, nmreq, nd->nd_len);
+ nfsmerr_if(error);
+ nd->nd_eoff = nd->nd_off + nd->nd_len;
+
+ if (nd->nd_len > 0) {
+ error = nfsm_chain_trim_data(nmreq, nd->nd_len, &mlen);
+ nfsmerr_if(error);
+ } else {
+ mlen = 0;
+ }
+
+ if ((nd->nd_len > NFSRV_MAXDATA) || (nd->nd_len < 0) || (mlen < nd->nd_len)) {
+ error = EIO;
+nfsmerr:
+ nd->nd_repstat = error;
+ error = nfsrv_rephead(nd, slp, &nmrep, NFSX_WCCDATA(nd->nd_vers));
+ if (!error) {
+ nd->nd_mrep = nmrep.nmc_mhead;
+ if (nd->nd_vers == NFS_VER3) {
+ nfsm_chain_add_wcc_data(error, nd, &nmrep,
+ preattrerr, &preattr, postattrerr, &postattr);
+ }
+ }
+ nfsm_chain_build_done(error, &nmrep);
+ nd->nd_time = 1;
+ }
+
+ /*
+ * Add this entry to the hash and time queues.
+ */
+ lck_mtx_lock(&slp->ns_wgmutex);
+ owp = NULL;
+ wp = slp->ns_tq.lh_first;
+ while (wp && wp->nd_time < nd->nd_time) {
+ owp = wp;
+ wp = wp->nd_tq.le_next;
+ }
+ if (owp) {
+ LIST_INSERT_AFTER(owp, nd, nd_tq);
+ } else {
+ LIST_INSERT_HEAD(&slp->ns_tq, nd, nd_tq);
+ }
+ if (!error) {
+ wpp = NWDELAYHASH(slp, nd->nd_fh.nfh_fid);
+ owp = NULL;
+ wp = wpp->lh_first;
+ while (wp && !nfsrv_fhmatch(&nd->nd_fh, &wp->nd_fh)) {
+ owp = wp;
+ wp = wp->nd_hash.le_next;
+ }
+ while (wp && (wp->nd_off < nd->nd_off) &&
+ nfsrv_fhmatch(&nd->nd_fh, &wp->nd_fh)) {
+ owp = wp;
+ wp = wp->nd_hash.le_next;
+ }
+ if (owp) {
+ LIST_INSERT_AFTER(owp, nd, nd_hash);
+ /*
+ * Search the hash list for overlapping entries and
+ * coalesce.
+ */
+ for (; nd && NFSW_CONTIG(owp, nd); nd = wp) {
+ wp = nd->nd_hash.le_next;
+ if (NFSW_SAMECRED(owp, nd)) {
+ nfsrv_wg_coalesce(owp, nd);
+ }
+ }
+ } else {
+ LIST_INSERT_HEAD(wpp, nd, nd_hash);
+ }
+ }
+ } else {
+ lck_mtx_lock(&slp->ns_wgmutex);
+ }
+
+ /*
+ * Now, do VNOP_WRITE()s for any one(s) that need to be done now
+ * and generate the associated reply mbuf list(s).
+ */
+loop1:
+ microuptime(&now);
+ cur_usec = (u_quad_t)now.tv_sec * 1000000 + (u_quad_t)now.tv_usec;
+ for (nd = slp->ns_tq.lh_first; nd; nd = owp) {
+ owp = nd->nd_tq.le_next;
+ if (nd->nd_time > cur_usec) {
+ break;
+ }
+ if (nd->nd_mrep) {
+ continue;
+ }
+ LIST_REMOVE(nd, nd_tq);
+ LIST_REMOVE(nd, nd_hash);
+ nmreq = &nd->nd_nmreq;
+ preattrerr = postattrerr = ENOENT;
+
+ /* save the incoming uid before mapping, */
+ /* for updating active user stats later */
+ saved_uid = kauth_cred_getuid(nd->nd_cr);
+
+ error = nfsrv_fhtovp(&nd->nd_fh, nd, &vp, &nx, &nxo);
+ if (!error) {
+ /* update per-export stats */
+ NFSStatAdd64(&nx->nx_stats.ops, 1);
+
+ error = nfsrv_credcheck(nd, ctx, nx, nxo);
+ if (error) {
+ vnode_put(vp);
+ }
+ }
+ if (!error) {
+ if (nd->nd_vers == NFS_VER3) {
+ nfsm_srv_pre_vattr_init(&preattr);
+ preattrerr = vnode_getattr(vp, &preattr, ctx);
+ }
+ if (vnode_vtype(vp) != VREG) {
+ if (nd->nd_vers == NFS_VER3) {
+ error = EINVAL;
+ } else {
+ error = (vnode_vtype(vp) == VDIR) ? EISDIR : EACCES;
+ }
+ }
+ } else {
+ vp = NULL;
+ }
+ if (!error) {
+ error = nfsrv_authorize(vp, NULL, KAUTH_VNODE_WRITE_DATA, ctx, nxo, 1);
+ }
+
+ if (nd->nd_stable == NFS_WRITE_UNSTABLE) {
+ ioflags = IO_NODELOCKED;
+ } else if (nd->nd_stable == NFS_WRITE_DATASYNC) {
+ ioflags = (IO_SYNC | IO_NODELOCKED);
+ } else {
+ ioflags = (IO_METASYNC | IO_SYNC | IO_NODELOCKED);
+ }
+
+ if (!error && ((nd->nd_eoff - nd->nd_off) > 0)) {
+ for (i = 0, m = nmreq->nmc_mhead; m; m = mbuf_next(m)) {
+ if (mbuf_len(m) > 0) {
+ i++;
+ }
+ }
+
+ MALLOC(uio_bufp, char *, UIO_SIZEOF(i), M_TEMP, M_WAITOK);
+ if (uio_bufp) {
+ auio = uio_createwithbuffer(i, nd->nd_off, UIO_SYSSPACE,
+ UIO_WRITE, uio_bufp, UIO_SIZEOF(i));
+ }
+ if (!uio_bufp || !auio) {
+ error = ENOMEM;
+ }
+ if (!error) {
+ for (m = nmreq->nmc_mhead; m; m = mbuf_next(m)) {
+ if ((tlen = mbuf_len(m)) > 0) {
+ uio_addiov(auio, CAST_USER_ADDR_T((caddr_t)mbuf_data(m)), tlen);
+ }
+ }
+ error = VNOP_WRITE(vp, auio, ioflags, ctx);
+ OSAddAtomic64(1, &nfsstats.srvvop_writes);
+
+ /* update export stats */
+ NFSStatAdd64(&nx->nx_stats.bytes_written, nd->nd_len);
+ /* update active user stats */
+ nfsrv_update_user_stat(nx, nd, saved_uid, 1, 0, nd->nd_len);
+
+#if CONFIG_FSE
+ if (nfsrv_fsevents_enabled && !error && need_fsevent(FSE_CONTENT_MODIFIED, vp)) {
+ nfsrv_modified(vp, ctx);
+ }
+#endif
+ }
+ if (uio_bufp) {
+ FREE(uio_bufp, M_TEMP);
+ uio_bufp = NULL;
+ }
+ }
+ if (vp) {
+ nfsm_srv_vattr_init(&postattr, nd->nd_vers);
+ postattrerr = vnode_getattr(vp, &postattr, ctx);
+ vnode_put(vp);
+ }
+
+ /*
+ * Loop around generating replies for all write rpcs that have
+ * now been completed.
+ */
+ swp = nd;
+ do {
+ if (error) {
+ nd->nd_repstat = error;
+ error = nfsrv_rephead(nd, slp, &nmrep, NFSX_WCCDATA(nd->nd_vers));
+ if (!error && (nd->nd_vers == NFS_VER3)) {
+ nfsm_chain_add_wcc_data(error, nd, &nmrep,
+ preattrerr, &preattr, postattrerr, &postattr);
+ }
+ } else {
+ nd->nd_repstat = error;
+ error = nfsrv_rephead(nd, slp, &nmrep, NFSX_PREOPATTR(nd->nd_vers) +
+ NFSX_POSTOPORFATTR(nd->nd_vers) + 2 * NFSX_UNSIGNED +
+ NFSX_WRITEVERF(nd->nd_vers));
+ if (!error && (nd->nd_vers == NFS_VER3)) {
+ nfsm_chain_add_wcc_data(error, nd, &nmrep,
+ preattrerr, &preattr, postattrerr, &postattr);
+ nfsm_chain_add_32(error, &nmrep, nd->nd_len);
+ nfsm_chain_add_32(error, &nmrep, nd->nd_stable);
+ /* write verifier */
+ nfsm_chain_add_32(error, &nmrep, nx->nx_exptime.tv_sec);
+ nfsm_chain_add_32(error, &nmrep, nx->nx_exptime.tv_usec);
+ } else if (!error) {
+ error = nfsm_chain_add_fattr(nd, &nmrep, &postattr);
+ }
+ }
+ nfsm_chain_build_done(error, &nmrep);
+ nfsmerr_if(error);
+ nd->nd_mrep = nmrep.nmc_mhead;
+
+ /*
+ * Done. Put it at the head of the timer queue so that
+ * the final phase can return the reply.
+ */
+ if (nd != swp) {
+ nd->nd_time = 1;
+ LIST_INSERT_HEAD(&slp->ns_tq, nd, nd_tq);
+ }
+ nd = swp->nd_coalesce.lh_first;
+ if (nd) {
+ LIST_REMOVE(nd, nd_tq);
+ }
+ } while (nd);
+ swp->nd_time = 1;
+ LIST_INSERT_HEAD(&slp->ns_tq, swp, nd_tq);
+ goto loop1;
+ }
+
+ /*
+ * Search for a reply to return.
+ */
+ for (nd = slp->ns_tq.lh_first; nd; nd = nd->nd_tq.le_next) {
+ if (nd->nd_mrep) {
+ LIST_REMOVE(nd, nd_tq);
+ *mrepp = nd->nd_mrep;
+ *ndp = nd;
+ break;
+ }
+ }
+ slp->ns_wgtime = slp->ns_tq.lh_first ? slp->ns_tq.lh_first->nd_time : 0;
+ lck_mtx_unlock(&slp->ns_wgmutex);
+
+ /*
+ * If we've just created a write pending gather,
+ * start the timer to check on it soon to make sure
+ * the write will be completed.
+ *
+ * Add/Remove the socket in the nfsrv_sockwg queue as needed.
+ */
+ lck_mtx_lock(nfsd_mutex);
+ if (slp->ns_wgtime) {
+ if (slp->ns_wgq.tqe_next == SLPNOLIST) {
+ TAILQ_INSERT_HEAD(&nfsrv_sockwg, slp, ns_wgq);
+ }
+ if (!nfsrv_wg_timer_on) {
+ nfsrv_wg_timer_on = 1;
+ nfs_interval_timer_start(nfsrv_wg_timer_call,
+ NFSRV_WGATHERDELAY);
+ }
+ } else if (slp->ns_wgq.tqe_next != SLPNOLIST) {
+ TAILQ_REMOVE(&nfsrv_sockwg, slp, ns_wgq);
+ slp->ns_wgq.tqe_next = SLPNOLIST;
+ }
+ lck_mtx_unlock(nfsd_mutex);
+
+ return 0;
+}
+
+/*
+ * Coalesce the write request nd into owp. To do this we must:
+ * - remove nd from the queues
+ * - merge nd->nd_nmreq into owp->nd_nmreq
+ * - update the nd_eoff and nd_stable for owp
+ * - put nd on owp's nd_coalesce list
+ */
+int
+nfsrv_wg_coalesce(struct nfsrv_descript *owp, struct nfsrv_descript *nd)
+{
+ int overlap, error;
+ mbuf_t mp, mpnext;
+ struct nfsrv_descript *p;
+
+ LIST_REMOVE(nd, nd_hash);
+ LIST_REMOVE(nd, nd_tq);
+ if (owp->nd_eoff < nd->nd_eoff) {
+ overlap = owp->nd_eoff - nd->nd_off;
+ if (overlap < 0) {
+ return EIO;
+ }
+ if (overlap > 0) {
+ mbuf_adj(nd->nd_nmreq.nmc_mhead, overlap);
+ }
+ mp = owp->nd_nmreq.nmc_mhead;
+ while ((mpnext = mbuf_next(mp))) {
+ mp = mpnext;
+ }
+ error = mbuf_setnext(mp, nd->nd_nmreq.nmc_mhead);
+ if (error) {
+ return error;
+ }
+ owp->nd_eoff = nd->nd_eoff;
+ } else {
+ mbuf_freem(nd->nd_nmreq.nmc_mhead);
+ }
+ nd->nd_nmreq.nmc_mhead = NULL;
+ nd->nd_nmreq.nmc_mcur = NULL;
+ if (nd->nd_stable == NFS_WRITE_FILESYNC) {
+ owp->nd_stable = NFS_WRITE_FILESYNC;
+ } else if ((nd->nd_stable == NFS_WRITE_DATASYNC) &&
+ (owp->nd_stable == NFS_WRITE_UNSTABLE)) {
+ owp->nd_stable = NFS_WRITE_DATASYNC;
+ }
+ LIST_INSERT_HEAD(&owp->nd_coalesce, nd, nd_tq);
+
+ /*
+ * If nd had anything else coalesced into it, transfer them
+ * to owp, otherwise their replies will never get sent.
+ */
+ while ((p = nd->nd_coalesce.lh_first)) {
+ LIST_REMOVE(p, nd_tq);
+ LIST_INSERT_HEAD(&owp->nd_coalesce, p, nd_tq);
+ }
+ return 0;
+}
+
+/*
+ * Scan the write gathering queues for writes that need to be
+ * completed now.
+ */
+void
+nfsrv_wg_timer(__unused void *param0, __unused void *param1)
+{
+ struct timeval now;
+ uint64_t cur_usec, next_usec;
+ int interval;
+ struct nfsrv_sock *slp;
+ int writes_pending = 0;
+
+ microuptime(&now);
+ cur_usec = (uint64_t)now.tv_sec * 1000000 + (uint64_t)now.tv_usec;
+ next_usec = cur_usec + (NFSRV_WGATHERDELAY * 1000);
+
+ lck_mtx_lock(nfsd_mutex);
+ TAILQ_FOREACH(slp, &nfsrv_sockwg, ns_wgq) {
+ if (slp->ns_wgtime) {
+ writes_pending++;
+ if (slp->ns_wgtime <= cur_usec) {
+ lck_rw_lock_exclusive(&slp->ns_rwlock);
+ slp->ns_flag |= SLP_DOWRITES;
+ lck_rw_done(&slp->ns_rwlock);
+ nfsrv_wakenfsd(slp);
+ continue;
+ }
+ if (slp->ns_wgtime < next_usec) {
+ next_usec = slp->ns_wgtime;
+ }
+ }
+ }
+
+ if (writes_pending == 0) {
+ nfsrv_wg_timer_on = 0;
+ lck_mtx_unlock(nfsd_mutex);
+ return;
+ }
+ lck_mtx_unlock(nfsd_mutex);
+
+ /*
+ * Return the number of msec to wait again
+ */
+ interval = (next_usec - cur_usec) / 1000;
+ if (interval < 1) {
+ interval = 1;
+ }
+ nfs_interval_timer_start(nfsrv_wg_timer_call, interval);
+}
+
+/*
+ * Sort the group list in increasing numerical order.
+ * (Insertion sort by Chris Torek, who was grossed out by the bubble sort
+ * that used to be here.)
+ */
+void
+nfsrv_group_sort(gid_t *list, int num)
+{
+ int i, j;
+ gid_t v;
+
+ /* Insertion sort. */
+ for (i = 1; i < num; i++) {
+ v = list[i];
+ /* find correct slot for value v, moving others up */
+ for (j = i; --j >= 0 && v < list[j];) {
+ list[j + 1] = list[j];
+ }
+ list[j + 1] = v;
+ }
+}
+
+/*
+ * nfs create service
+ * now does a truncate to 0 length via. setattr if it already exists
+ */
+int
+nfsrv_create(
+ struct nfsrv_descript *nd,
+ struct nfsrv_sock *slp,
+ vfs_context_t ctx,
+ mbuf_t *mrepp)
+{
+ struct vnode_attr dpreattr, dpostattr, postattr;
+ struct vnode_attr va, *vap = &va;
+ struct nameidata ni;
+ int error, rdev, dpreattrerr, dpostattrerr, postattrerr;
+ int how, exclusive_flag;
+ uint32_t len = 0, cnflags;
+ vnode_t vp, dvp, dirp;
+ struct nfs_filehandle nfh;
+ struct nfs_export *nx = NULL;
+ struct nfs_export_options *nxo;
+ u_quad_t tempsize;
+ u_char cverf[NFSX_V3CREATEVERF];
+ uid_t saved_uid;
+ struct nfsm_chain *nmreq, nmrep;
+
+ error = 0;
+ dpreattrerr = dpostattrerr = postattrerr = ENOENT;
+ nmreq = &nd->nd_nmreq;
+ nfsm_chain_null(&nmrep);
+ vp = dvp = dirp = NULL;
+ exclusive_flag = 0;
+ ni.ni_cnd.cn_nameiop = 0;
+ rdev = 0;
+
+ saved_uid = kauth_cred_getuid(nd->nd_cr);
+
+ nfsm_chain_get_fh_ptr(error, nmreq, nd->nd_vers, nfh.nfh_fhp, nfh.nfh_len);
+ nfsm_chain_get_32(error, nmreq, len);
+ nfsm_name_len_check(error, nd, len);
+ nfsmerr_if(error);
+
+ ni.ni_cnd.cn_nameiop = CREATE;
+#if CONFIG_TRIGGERS
+ ni.ni_op = OP_LINK;
+#endif
+ ni.ni_cnd.cn_flags = LOCKPARENT | LOCKLEAF;
+ ni.ni_cnd.cn_ndp = ∋
+
+ error = nfsm_chain_get_path_namei(nmreq, len, &ni);
+ if (!error) {
+ error = nfsrv_namei(nd, ctx, &ni, &nfh, &dirp, &nx, &nxo);
+ if (nx != NULL) {
+ /* update export stats */
+ NFSStatAdd64(&nx->nx_stats.ops, 1);
+
+ /* update active user stats */
+ nfsrv_update_user_stat(nx, nd, saved_uid, 1, 0, 0);
+ }
+ }
+ if (dirp) {
+ if (nd->nd_vers == NFS_VER3) {
+ nfsm_srv_pre_vattr_init(&dpreattr);
+ dpreattrerr = vnode_getattr(dirp, &dpreattr, ctx);
+ } else {
+ vnode_put(dirp);
+ dirp = NULL;
+ }
+ }
+
+ if (error) {
+ ni.ni_cnd.cn_nameiop = 0;
+ goto nfsmerr;
+ }
+
+ dvp = ni.ni_dvp;
+ vp = ni.ni_vp;
+ VATTR_INIT(vap);
+
+ if (nd->nd_vers == NFS_VER3) {
+ nfsm_chain_get_32(error, nmreq, how);
+ nfsmerr_if(error);
+ switch (how) {
+ case NFS_CREATE_GUARDED:
+ if (vp) {
+ error = EEXIST;
+ break;
+ }
+ case NFS_CREATE_UNCHECKED:
+ error = nfsm_chain_get_sattr(nd, nmreq, vap);
+ break;
+ case NFS_CREATE_EXCLUSIVE:
+ nfsm_chain_get_opaque(error, nmreq, NFSX_V3CREATEVERF, cverf);
+ exclusive_flag = 1;
+ if (vp == NULL) {
+ VATTR_SET(vap, va_mode, 0);
+ }
+ break;
+ }
+ ;
+ VATTR_SET(vap, va_type, VREG);
+ } else {
+ enum vtype v_type;
+
+ error = nfsm_chain_get_sattr(nd, nmreq, vap);
+ nfsmerr_if(error);
+ v_type = vap->va_type;
+ if (v_type == VNON) {
+ v_type = VREG;
+ }
+ VATTR_SET(vap, va_type, v_type);
+
+ switch (v_type) {
+ case VCHR:
+ case VBLK:
+ case VFIFO:
+ rdev = vap->va_data_size;
+ VATTR_CLEAR_ACTIVE(vap, va_data_size);
+ break;
+ default:
+ break;
+ }
+ ;
+ }
+ nfsmerr_if(error);
+
+ /*
+ * If it doesn't exist, create it
+ * otherwise just truncate to 0 length
+ * should I set the mode too ??
+ */
+ if (vp == NULL) {
+ kauth_acl_t xacl = NULL;
+
+ /* authorize before creating */
+ error = nfsrv_authorize(dvp, NULL, KAUTH_VNODE_ADD_FILE, ctx, nxo, 0);
+
+ /* construct ACL and handle inheritance */
+ if (!error) {
+ error = kauth_acl_inherit(dvp,
+ NULL,
+ &xacl,
+ 0 /* !isdir */,
+ ctx);
+
+ if (!error && xacl != NULL) {
+ VATTR_SET(vap, va_acl, xacl);
+ }
+ }
+ VATTR_CLEAR_ACTIVE(vap, va_data_size);
+ VATTR_CLEAR_ACTIVE(vap, va_access_time);
+ /*
+ * Server policy is to alway use the mapped rpc credential for
+ * file system object creation. This has the nice side effect of
+ * enforcing BSD creation semantics
+ */
+ VATTR_CLEAR_ACTIVE(vap, va_uid);
+ VATTR_CLEAR_ACTIVE(vap, va_gid);
+
+ /* validate new-file security information */
+ if (!error) {
+ error = vnode_authattr_new(dvp, vap, 0, ctx);
+ }
+
+ if (!error) {
+ error = vn_authorize_create(dvp, &ni.ni_cnd, vap, ctx, NULL);
+ if (error) {
+ error = EACCES;
+ }
+ }
+
+ if (vap->va_type == VREG || vap->va_type == VSOCK) {
+ if (!error) {
+ error = VNOP_CREATE(dvp, &vp, &ni.ni_cnd, vap, ctx);
+ }
+
+ if (!error && !VATTR_ALL_SUPPORTED(vap)) {
+ /*
+ * If some of the requested attributes weren't handled by the VNOP,
+ * use our fallback code.
+ */
+ error = vnode_setattr_fallback(vp, vap, ctx);
+ }
+
+ if (xacl != NULL) {
+ kauth_acl_free(xacl);
+ }
+
+ if (!error) {
+ if (exclusive_flag) {
+ exclusive_flag = 0;
+ VATTR_INIT(vap);
+ bcopy(cverf, (caddr_t)&vap->va_access_time,
+ NFSX_V3CREATEVERF);
+ VATTR_SET_ACTIVE(vap, va_access_time);
+ // skip authorization, as this is an
+ // NFS internal implementation detail.
+ error = vnode_setattr(vp, vap, ctx);
+ }
+
+#if CONFIG_FSE
+ if (nfsrv_fsevents_enabled && need_fsevent(FSE_CREATE_FILE, vp)) {
+ add_fsevent(FSE_CREATE_FILE, ctx,
+ FSE_ARG_VNODE, vp,
+ FSE_ARG_DONE);
+ }
+#endif
+ }
+ } else if (vap->va_type == VCHR || vap->va_type == VBLK ||
+ vap->va_type == VFIFO) {
+ if (vap->va_type == VCHR && rdev == (int)0xffffffff) {
+ VATTR_SET(vap, va_type, VFIFO);
+ }
+ if (vap->va_type != VFIFO) {
+ error = suser(nd->nd_cr, NULL);
+ nfsmerr_if(error);
+ }
+ VATTR_SET(vap, va_rdev, (dev_t)rdev);
+
+ error = VNOP_MKNOD(dvp, &vp, &ni.ni_cnd, vap, ctx);
+
+ if (xacl != NULL) {
+ kauth_acl_free(xacl);
+ }
+
+ nfsmerr_if(error);
+
+ if (vp) {
+ vnode_recycle(vp);
+ vnode_put(vp);
+ vp = NULL;
+ }
+ ni.ni_cnd.cn_nameiop = LOOKUP;
+#if CONFIG_TRIGGERS
+ ni.ni_op = OP_LOOKUP;
+#endif
+ ni.ni_cnd.cn_flags &= ~LOCKPARENT;
+ ni.ni_cnd.cn_context = ctx;
+ ni.ni_startdir = dvp;
+ ni.ni_usedvp = dvp;
+ ni.ni_rootdir = rootvnode;
+ cnflags = ni.ni_cnd.cn_flags; /* store in case we have to restore */
+ while ((error = lookup(&ni)) == ERECYCLE) {
+ ni.ni_cnd.cn_flags = cnflags;
+ ni.ni_cnd.cn_nameptr = ni.ni_cnd.cn_pnbuf;
+ ni.ni_usedvp = ni.ni_dvp = ni.ni_startdir = dvp;
+ }
+ if (!error) {
+ if (ni.ni_cnd.cn_flags & ISSYMLINK) {
+ error = EINVAL;
+ }
+ vp = ni.ni_vp;
+ }
+ nfsmerr_if(error);
+ } else {
+ error = ENXIO;
+ }
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&ni);
+ ni.ni_cnd.cn_nameiop = 0;
+
+ vnode_put(dvp);
+ } else {
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&ni);
+ ni.ni_cnd.cn_nameiop = 0;
+
+ vnode_put(dvp);
+
+#if CONFIG_MACF
+ if (!error && VATTR_IS_ACTIVE(vap, va_data_size)) {
+ /* NOTE: File has not been open for NFS case, so NOCRED for filecred */
+ error = mac_vnode_check_truncate(ctx, NOCRED, vp);
+ if (error) {
+ error = EACCES;
+ }
+ }
+#endif
+ if (!error && VATTR_IS_ACTIVE(vap, va_data_size)) {
+ error = nfsrv_authorize(vp, NULL, KAUTH_VNODE_WRITE_DATA,
+ ctx, nxo, 0);
+ if (!error) {
+ tempsize = vap->va_data_size;
+ VATTR_INIT(vap);
+ VATTR_SET(vap, va_data_size, tempsize);
+ error = vnode_setattr(vp, vap, ctx);
+ }
+ }
+ }
+ if (!error) {
+ error = nfsrv_vptofh(nx, nd->nd_vers, NULL, vp, ctx, &nfh);
+ if (!error) {
+ nfsm_srv_vattr_init(&postattr, nd->nd_vers);
+ postattrerr = vnode_getattr(vp, &postattr, ctx);
+ if (nd->nd_vers == NFS_VER2) {
+ error = postattrerr;
+ }
+ }
+ }
+ if (vp) {
+ vnode_put(vp);
+ }
+
+ if (nd->nd_vers == NFS_VER3) {
+ if (exclusive_flag && !error &&
+ bcmp(cverf, &postattr.va_access_time, NFSX_V3CREATEVERF)) {
+ error = EEXIST;
+ }
+ nfsm_srv_vattr_init(&dpostattr, NFS_VER3);
+ dpostattrerr = vnode_getattr(dirp, &dpostattr, ctx);
+ vnode_put(dirp);
+ dirp = NULL;
+ }
+
+nfsmerr:
+ /* assemble reply */
+ nd->nd_repstat = error;
+ error = nfsrv_rephead(nd, slp, &nmrep, NFSX_SRVFH(nd->nd_vers, &nfh) +
+ NFSX_FATTR(nd->nd_vers) + NFSX_WCCDATA(nd->nd_vers));
+ nfsmout_if(error);
+ *mrepp = nmrep.nmc_mhead;
+ nfsmout_on_status(nd, error);
+ if (nd->nd_vers == NFS_VER3) {
+ if (!nd->nd_repstat) {
+ nfsm_chain_add_postop_fh(error, &nmrep, nfh.nfh_fhp, nfh.nfh_len);
+ nfsm_chain_add_postop_attr(error, nd, &nmrep, postattrerr, &postattr);
+ }
+ nfsm_chain_add_wcc_data(error, nd, &nmrep,
+ dpreattrerr, &dpreattr, dpostattrerr, &dpostattr);
+ } else {
+ nfsm_chain_add_fh(error, &nmrep, NFS_VER2, nfh.nfh_fhp, nfh.nfh_len);
+ if (!error) {
+ error = nfsm_chain_add_fattr(nd, &nmrep, &postattr);
+ }
+ }
+nfsmout:
+ nfsm_chain_build_done(error, &nmrep);
+ if (ni.ni_cnd.cn_nameiop) {
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&ni);
+
+ if (vp) {
+ vnode_put(vp);
+ }
+ vnode_put(dvp);
+ }
+ if (dirp) {
+ vnode_put(dirp);
+ }
+ if (error) {
+ nfsm_chain_cleanup(&nmrep);
+ *mrepp = NULL;
+ }
+ return error;
+}
+
+/*
+ * nfs v3 mknod service
+ */
+int
+nfsrv_mknod(
+ struct nfsrv_descript *nd,
+ struct nfsrv_sock *slp,
+ vfs_context_t ctx,
+ mbuf_t *mrepp)
+{
+ struct vnode_attr dpreattr, dpostattr, postattr;
+ struct vnode_attr va, *vap = &va;
+ struct nameidata ni;
+ int error, dpreattrerr, dpostattrerr, postattrerr;
+ uint32_t len = 0, cnflags;
+ u_int32_t major = 0, minor = 0;
+ enum vtype vtyp;
+ nfstype nvtype;
+ vnode_t vp, dvp, dirp;
+ struct nfs_filehandle nfh;
+ struct nfs_export *nx = NULL;
+ struct nfs_export_options *nxo;
+ uid_t saved_uid;
+ kauth_acl_t xacl = NULL;
+ struct nfsm_chain *nmreq, nmrep;
+
+ error = 0;
+ dpreattrerr = dpostattrerr = postattrerr = ENOENT;
+ nmreq = &nd->nd_nmreq;
+ nfsm_chain_null(&nmrep);
+ vp = dvp = dirp = NULL;
+ ni.ni_cnd.cn_nameiop = 0;
+
+ saved_uid = kauth_cred_getuid(nd->nd_cr);
+
+ nfsm_chain_get_fh_ptr(error, nmreq, NFS_VER3, nfh.nfh_fhp, nfh.nfh_len);
+ nfsm_chain_get_32(error, nmreq, len);
+ nfsm_name_len_check(error, nd, len);
+ nfsmerr_if(error);
+
+ ni.ni_cnd.cn_nameiop = CREATE;
+#if CONFIG_TRIGGERS
+ ni.ni_op = OP_LINK;
+#endif
+ ni.ni_cnd.cn_flags = LOCKPARENT | LOCKLEAF;
+ ni.ni_cnd.cn_ndp = ∋
+ error = nfsm_chain_get_path_namei(nmreq, len, &ni);
+ if (!error) {
+ error = nfsrv_namei(nd, ctx, &ni, &nfh, &dirp, &nx, &nxo);
+ if (nx != NULL) {
+ /* update export stats */
+ NFSStatAdd64(&nx->nx_stats.ops, 1);
+
+ /* update active user stats */
+ nfsrv_update_user_stat(nx, nd, saved_uid, 1, 0, 0);
+ }
+ }
+ if (dirp) {
+ nfsm_srv_pre_vattr_init(&dpreattr);
+ dpreattrerr = vnode_getattr(dirp, &dpreattr, ctx);
+ }
+ if (error) {
+ ni.ni_cnd.cn_nameiop = 0;
+ goto nfsmerr;
+ }
+
+ dvp = ni.ni_dvp;
+ vp = ni.ni_vp;
+
+ nfsm_chain_get_32(error, nmreq, nvtype);
+ nfsmerr_if(error);
+ vtyp = nfstov_type(nvtype, NFS_VER3);
+ if (!error && (vtyp != VCHR) && (vtyp != VBLK) && (vtyp != VSOCK) && (vtyp != VFIFO)) {
+ error = NFSERR_BADTYPE;
+ goto out;
+ }
+
+ VATTR_INIT(vap);
+ error = nfsm_chain_get_sattr(nd, nmreq, vap);
+ if ((vtyp == VCHR) || (vtyp == VBLK)) {
+ nfsm_chain_get_32(error, nmreq, major);
+ nfsm_chain_get_32(error, nmreq, minor);
+ nfsmerr_if(error);
+ VATTR_SET(vap, va_rdev, makedev(major, minor));
+ }
+ nfsmerr_if(error);
+
+ /*
+ * If it doesn't exist, create it.
+ */
+ if (vp) {
+ error = EEXIST;
+ goto out;
+ }
+ VATTR_SET(vap, va_type, vtyp);
+
+ /* authorize before creating */
+ error = nfsrv_authorize(dvp, NULL, KAUTH_VNODE_ADD_FILE, ctx, nxo, 0);
+
+ /* construct ACL and handle inheritance */
+ if (!error) {
+ error = kauth_acl_inherit(dvp,
+ NULL,
+ &xacl,
+ 0 /* !isdir */,
+ ctx);
+
+ if (!error && xacl != NULL) {
+ VATTR_SET(vap, va_acl, xacl);
+ }
+ }
+ VATTR_CLEAR_ACTIVE(vap, va_data_size);
+ VATTR_CLEAR_ACTIVE(vap, va_access_time);
+ /*
+ * Server policy is to alway use the mapped rpc credential for
+ * file system object creation. This has the nice side effect of
+ * enforcing BSD creation semantics
+ */
+ VATTR_CLEAR_ACTIVE(vap, va_uid);
+ VATTR_CLEAR_ACTIVE(vap, va_gid);
+
+ /* validate new-file security information */
+ if (!error) {
+ error = vnode_authattr_new(dvp, vap, 0, ctx);
+ }
+ if (!error) {
+ error = vn_authorize_create(dvp, &ni.ni_cnd, vap, ctx, NULL);
+ if (error) {
+ error = EACCES;
+ }
+ }
+ if (error) {
+ goto out1;
+ }
+
+ if (vtyp == VSOCK) {
+ error = VNOP_CREATE(dvp, &vp, &ni.ni_cnd, vap, ctx);
+
+ if (!error && !VATTR_ALL_SUPPORTED(vap)) {
+ /*
+ * If some of the requested attributes weren't handled by the VNOP,
+ * use our fallback code.
+ */
+ error = vnode_setattr_fallback(vp, vap, ctx);
+ }
+ } else {
+ if (vtyp != VFIFO && (error = suser(nd->nd_cr, (u_short *)0))) {
+ goto out1;
+ }
+ if ((error = VNOP_MKNOD(dvp, &vp, &ni.ni_cnd, vap, ctx))) {
+ goto out1;
+ }
+ if (vp) {
+ vnode_recycle(vp);
+ vnode_put(vp);
+ vp = NULL;
+ }
+ ni.ni_cnd.cn_nameiop = LOOKUP;
+#if CONFIG_TRIGGERS
+ ni.ni_op = OP_LOOKUP;
+#endif
+ ni.ni_cnd.cn_flags &= ~LOCKPARENT;
+ ni.ni_cnd.cn_context = vfs_context_current();
+ ni.ni_startdir = dvp;
+ ni.ni_usedvp = dvp;
+ ni.ni_rootdir = rootvnode;
+ cnflags = ni.ni_cnd.cn_flags; /* store in case we have to restore */
+ while ((error = lookup(&ni)) == ERECYCLE) {
+ ni.ni_cnd.cn_flags = cnflags;
+ ni.ni_cnd.cn_nameptr = ni.ni_cnd.cn_pnbuf;
+ ni.ni_usedvp = ni.ni_dvp = ni.ni_startdir = dvp;
+ }
+ if (!error) {
+ vp = ni.ni_vp;
+ if (ni.ni_cnd.cn_flags & ISSYMLINK) {
+ error = EINVAL;
+ }
+ }
+ }
+out1:
+ if (xacl != NULL) {
+ kauth_acl_free(xacl);
+ }
+out:
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&ni);
+ ni.ni_cnd.cn_nameiop = 0;
+
+ vnode_put(dvp);
+ dvp = NULL;
+
+ if (!error) {
+ error = nfsrv_vptofh(nx, NFS_VER3, NULL, vp, ctx, &nfh);
+ if (!error) {
+ nfsm_srv_vattr_init(&postattr, NFS_VER3);
+ postattrerr = vnode_getattr(vp, &postattr, ctx);
+ }
+ }
+ if (vp) {
+ vnode_put(vp);
+ vp = NULL;
+ }
+
+ nfsm_srv_vattr_init(&dpostattr, NFS_VER3);
+ dpostattrerr = vnode_getattr(dirp, &dpostattr, ctx);
+ vnode_put(dirp);
+ dirp = NULL;
+
+nfsmerr:
+ /* assemble reply */
+ nd->nd_repstat = error;
+ error = nfsrv_rephead(nd, slp, &nmrep, NFSX_SRVFH(NFS_VER3, &nfh) +
+ NFSX_POSTOPATTR(NFS_VER3) + NFSX_WCCDATA(NFS_VER3));
+ nfsmout_if(error);
+ *mrepp = nmrep.nmc_mhead;
+ nfsmout_on_status(nd, error);
+ if (!nd->nd_repstat) {
+ nfsm_chain_add_postop_fh(error, &nmrep, nfh.nfh_fhp, nfh.nfh_len);
+ nfsm_chain_add_postop_attr(error, nd, &nmrep, postattrerr, &postattr);
+ }
+ nfsm_chain_add_wcc_data(error, nd, &nmrep,
+ dpreattrerr, &dpreattr, dpostattrerr, &dpostattr);
+nfsmout:
+ nfsm_chain_build_done(error, &nmrep);
+ if (ni.ni_cnd.cn_nameiop) {
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&ni);
+
+ if (vp) {
+ vnode_put(vp);
+ }
+ vnode_put(dvp);
+ }
+ if (dvp) {
+ vnode_put(dvp);
+ }
+ if (vp) {
+ vnode_put(vp);
+ }
+ if (dirp) {
+ vnode_put(dirp);
+ }
+ if (error) {
+ nfsm_chain_cleanup(&nmrep);
+ *mrepp = NULL;
+ }
+ return error;
+}
+
+/*
+ * nfs remove service
+ */
+int
+nfsrv_remove(
+ struct nfsrv_descript *nd,
+ struct nfsrv_sock *slp,
+ vfs_context_t ctx,
+ mbuf_t *mrepp)
+{
+ struct nameidata ni;
+ int error, dpreattrerr, dpostattrerr;
+ uint32_t len = 0;
+ uid_t saved_uid;
+ vnode_t vp, dvp, dirp = NULL;
+ struct vnode_attr dpreattr, dpostattr;
+ struct nfs_filehandle nfh;
+ struct nfs_export *nx = NULL;
+ struct nfs_export_options *nxo;
+ struct nfsm_chain *nmreq, nmrep;
+
+ error = 0;
+ dpreattrerr = dpostattrerr = ENOENT;
+ saved_uid = kauth_cred_getuid(nd->nd_cr);
+ dvp = vp = dirp = NULL;
+ nmreq = &nd->nd_nmreq;
+ nfsm_chain_null(&nmrep);
+
+ nfsm_chain_get_fh_ptr(error, nmreq, nd->nd_vers, nfh.nfh_fhp, nfh.nfh_len);
+ nfsm_chain_get_32(error, nmreq, len);
+ nfsm_name_len_check(error, nd, len);
+ nfsmerr_if(error);
+
+ ni.ni_cnd.cn_nameiop = DELETE;
+#if CONFIG_TRIGGERS
+ ni.ni_op = OP_UNLINK;
+#endif
+ ni.ni_cnd.cn_flags = LOCKPARENT | LOCKLEAF;
+ ni.ni_cnd.cn_ndp = ∋
+ error = nfsm_chain_get_path_namei(nmreq, len, &ni);
+ if (!error) {
+ error = nfsrv_namei(nd, ctx, &ni, &nfh, &dirp, &nx, &nxo);
+ if (nx != NULL) {
+ /* update export stats */
+ NFSStatAdd64(&nx->nx_stats.ops, 1);
+
+ /* update active user stats */
+ nfsrv_update_user_stat(nx, nd, saved_uid, 1, 0, 0);
+ }
+ }
+ if (dirp) {
+ if (nd->nd_vers == NFS_VER3) {
+ nfsm_srv_pre_vattr_init(&dpreattr);
+ dpreattrerr = vnode_getattr(dirp, &dpreattr, ctx);
+ } else {
+ vnode_put(dirp);
+ dirp = NULL;
+ }
+ }
+
+ if (!error) {
+ dvp = ni.ni_dvp;
+ vp = ni.ni_vp;
+
+ if (vnode_vtype(vp) == VDIR) {
+ error = EPERM; /* POSIX */
+ } else if (vnode_isvroot(vp)) {
+ /*
+ * The root of a mounted filesystem cannot be deleted.
+ */
+ error = EBUSY;
+ } else {
+ error = nfsrv_authorize(vp, dvp, KAUTH_VNODE_DELETE, ctx, nxo, 0);
+ }
+
+ if (!error) {
+ error = vn_authorize_unlink(dvp, vp, &ni.ni_cnd, ctx, NULL);
+ if (error) {
+ error = EACCES;
+ }
+ }
+
+ if (!error) {
+#if CONFIG_FSE
+ char *path = NULL;
+ int plen;
+ fse_info finfo;
+
+ if (nfsrv_fsevents_enabled && need_fsevent(FSE_DELETE, dvp)) {
+ plen = MAXPATHLEN;
+ if ((path = get_pathbuff()) && !vn_getpath(vp, path, &plen)) {
+ get_fse_info(vp, &finfo, ctx);
+ } else if (path) {
+ release_pathbuff(path);
+ path = NULL;
+ }
+ }
+#endif
+ error = VNOP_REMOVE(dvp, vp, &ni.ni_cnd, 0, ctx);
+
+#if CONFIG_FSE
+ if (path) {
+ if (!error) {
+ add_fsevent(FSE_DELETE, ctx,
+ FSE_ARG_STRING, plen, path,
+ FSE_ARG_FINFO, &finfo,
+ FSE_ARG_DONE);
+ }
+ release_pathbuff(path);
+ }
+#endif
+ }
+
+ /*
+ * nameidone has to happen before we vnode_put(dvp)
+ * since it may need to release the fs_nodelock on the dvp
+ */
+ nameidone(&ni);
+
+ vnode_put(vp);
+ vnode_put(dvp);
+ }
+
+nfsmerr:
+ if (dirp) {
+ nfsm_srv_vattr_init(&dpostattr, nd->nd_vers);
+ dpostattrerr = vnode_getattr(dirp, &dpostattr, ctx);
+ vnode_put(dirp);
+ }
+
+ /* assemble reply */
+ nd->nd_repstat = error;
+ error = nfsrv_rephead(nd, slp, &nmrep, NFSX_WCCDATA(nd->nd_vers));
+ nfsmout_if(error);
+ *mrepp = nmrep.nmc_mhead;
+ nfsmout_on_status(nd, error);
+ if (nd->nd_vers == NFS_VER3) {
+ nfsm_chain_add_wcc_data(error, nd, &nmrep,
+ dpreattrerr, &dpreattr, dpostattrerr, &dpostattr);
+ }
+nfsmout:
+ nfsm_chain_build_done(error, &nmrep);
+ if (error) {
+ nfsm_chain_cleanup(&nmrep);
+ *mrepp = NULL;
+ }
+ return error;
+}
+
+/*
+ * nfs rename service
+ */
+int
+nfsrv_rename(
+ struct nfsrv_descript *nd,
+ struct nfsrv_sock *slp,
+ vfs_context_t ctx,
+ mbuf_t *mrepp)
+{
+ kauth_cred_t saved_cred = NULL;
+ uid_t saved_uid;
+ int error;
+ uint32_t fromlen, tolen;
+ int fdpreattrerr, fdpostattrerr;
+ int tdpreattrerr, tdpostattrerr;
+ char *frompath = NULL, *topath = NULL;
+ struct nameidata fromni, toni;
+ vnode_t fvp, tvp, tdvp, fdvp, fdirp, tdirp;
+ struct vnode_attr fdpreattr, fdpostattr;
+ struct vnode_attr tdpreattr, tdpostattr;
+ struct nfs_filehandle fnfh, tnfh;
+ struct nfs_export *fnx, *tnx;
+ struct nfs_export_options *fnxo, *tnxo;
+ enum vtype fvtype, tvtype;
+ int holding_mntlock;
+ mount_t locked_mp;
+ struct nfsm_chain *nmreq, nmrep;
+ char *from_name, *to_name;
+#if CONFIG_FSE
+ int from_len = 0, to_len = 0;
+ fse_info from_finfo, to_finfo;
+#endif
+ u_char didstats = 0;
+ const char *oname;
+
+ error = 0;
+ fdpreattrerr = fdpostattrerr = ENOENT;
+ tdpreattrerr = tdpostattrerr = ENOENT;
+ saved_uid = kauth_cred_getuid(nd->nd_cr);
+ fromlen = tolen = 0;
+ frompath = topath = NULL;
+ fdirp = tdirp = NULL;
+ nmreq = &nd->nd_nmreq;
+ nfsm_chain_null(&nmrep);
+
+ /*
+ * these need to be set before calling any code
+ * that they may take us out through the error path.
+ */
+ holding_mntlock = 0;
+ fvp = tvp = NULL;
+ fdvp = tdvp = NULL;
+ locked_mp = NULL;
+
+ nfsm_chain_get_fh_ptr(error, nmreq, nd->nd_vers, fnfh.nfh_fhp, fnfh.nfh_len);
+ nfsm_chain_get_32(error, nmreq, fromlen);
+ nfsm_name_len_check(error, nd, fromlen);
+ nfsmerr_if(error);
+ error = nfsm_chain_get_path_namei(nmreq, fromlen, &fromni);
+ nfsmerr_if(error);
+ frompath = fromni.ni_cnd.cn_pnbuf;
+
+ nfsm_chain_get_fh_ptr(error, nmreq, nd->nd_vers, tnfh.nfh_fhp, tnfh.nfh_len);
+ nfsm_chain_get_32(error, nmreq, tolen);
+ nfsm_name_len_check(error, nd, tolen);
+ nfsmerr_if(error);
+ error = nfsm_chain_get_path_namei(nmreq, tolen, &toni);
+ nfsmerr_if(error);
+ topath = toni.ni_cnd.cn_pnbuf;
+
+ /*
+ * Remember our original uid so that we can reset cr_uid before
+ * the second nfsrv_namei() call, in case it is remapped.
+ */
+ saved_cred = nd->nd_cr;
+ kauth_cred_ref(saved_cred);
+retry:
+ fromni.ni_cnd.cn_nameiop = DELETE;
+#if CONFIG_TRIGGERS
+ fromni.ni_op = OP_UNLINK;
+#endif
+ fromni.ni_cnd.cn_flags = WANTPARENT;
+
+ fromni.ni_cnd.cn_pnbuf = frompath;
+ frompath = NULL;
+ fromni.ni_cnd.cn_pnlen = MAXPATHLEN;
+ fromni.ni_cnd.cn_flags |= HASBUF;
+ fromni.ni_cnd.cn_ndp = &fromni;
+
+ error = nfsrv_namei(nd, ctx, &fromni, &fnfh, &fdirp, &fnx, &fnxo);
+ if (error) {
+ goto out;
+ }
+ fdvp = fromni.ni_dvp;
+ fvp = fromni.ni_vp;
+
+ if (fdirp) {
+ if (nd->nd_vers == NFS_VER3) {
+ nfsm_srv_pre_vattr_init(&fdpreattr);
+ fdpreattrerr = vnode_getattr(fdirp, &fdpreattr, ctx);
+ } else {
+ vnode_put(fdirp);
+ fdirp = NULL;
+ }
+ }
+ fvtype = vnode_vtype(fvp);
+
+ /* reset credential if it was remapped */
+ if (nd->nd_cr != saved_cred) {
+ kauth_cred_ref(saved_cred);
+ kauth_cred_unref(&nd->nd_cr);
+ ctx->vc_ucred = nd->nd_cr = saved_cred;
+ }
+
+ toni.ni_cnd.cn_nameiop = RENAME;
+#if CONFIG_TRIGGERS
+ toni.ni_op = OP_RENAME;
+#endif
+ toni.ni_cnd.cn_flags = WANTPARENT;
+
+ toni.ni_cnd.cn_pnbuf = topath;
+ topath = NULL;
+ toni.ni_cnd.cn_pnlen = MAXPATHLEN;
+ toni.ni_cnd.cn_flags |= HASBUF;
+ toni.ni_cnd.cn_ndp = &toni;
+
+ if (fvtype == VDIR) {
+ toni.ni_cnd.cn_flags |= WILLBEDIR;
+ }
+
+ tnx = NULL;
+ error = nfsrv_namei(nd, ctx, &toni, &tnfh, &tdirp, &tnx, &tnxo);
+ if (error) {
+ /*
+ * Translate error code for rename("dir1", "dir2/.").
+ */
+ if (error == EISDIR && fvtype == VDIR) {
+ if (nd->nd_vers == NFS_VER3) {
+ error = EINVAL;
+ } else {
+ error = ENOTEMPTY;
+ }
+ }
+ goto out;
+ }
+ tdvp = toni.ni_dvp;
+ tvp = toni.ni_vp;
+
+ if (!didstats) {
+ /* update export stats once only */
+ if (tnx != NULL) {
+ /* update export stats */
+ NFSStatAdd64(&tnx->nx_stats.ops, 1);
+
+ /* update active user stats */
+ nfsrv_update_user_stat(tnx, nd, saved_uid, 1, 0, 0);
+ didstats = 1;
+ }
+ }
+
+ if (tdirp) {
+ if (nd->nd_vers == NFS_VER3) {
+ nfsm_srv_pre_vattr_init(&tdpreattr);
+ tdpreattrerr = vnode_getattr(tdirp, &tdpreattr, ctx);
+ } else {
+ vnode_put(tdirp);
+ tdirp = NULL;
+ }
+ }
+
+ if (tvp != NULL) {
+ tvtype = vnode_vtype(tvp);
+
+ if (fvtype == VDIR && tvtype != VDIR) {
+ if (nd->nd_vers == NFS_VER3) {
+ error = EEXIST;
+ } else {
+ error = EISDIR;
+ }
+ goto out;
+ } else if (fvtype != VDIR && tvtype == VDIR) {
+ if (nd->nd_vers == NFS_VER3) {
+ error = EEXIST;
+ } else {
+ error = ENOTDIR;
+ }