+ struct nfsrv_descript *nd, *wp, *owp, *swp;
+ struct nfs_export *nx;
+ struct nfs_export_options *nxo;
+ struct nfsrv_wg_delayhash *wpp;
+ uid_t saved_uid;
+ struct vnode_attr preattr, postattr;
+ int error, mlen, i, ioflags;
+ size_t tlen;
+ int preattrerr, postattrerr;
+ vnode_t vp;
+ mbuf_t m;
+ uio_t auio = NULL;
+ char *uio_bufp = NULL;
+ time_t cur_usec;
+ struct timeval now;
+ struct nfsm_chain *nmreq, nmrep;
+
+ 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 = now.tv_sec * 1000000 + 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 = now.tv_sec * 1000000 + 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 error;
+ off_t overlap;
+ 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, (int)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;
+ time_t cur_usec, next_usec;
+ time_t interval;
+ struct nfsrv_sock *slp;
+ int writes_pending = 0;
+
+ microuptime(&now);
+ cur_usec = now.tv_sec * 1000000 + 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, dpreattrerr, dpostattrerr, postattrerr;
+ int how, exclusive_flag;
+ uint32_t len = 0, cnflags;
+ uint64_t rdev;
+ vnode_t vp, dvp, dirp;
+ struct nfs_filehandle nfh;
+ struct nfs_export *nx = NULL;
+ struct nfs_export_options *nxo = NULL;
+ 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;
+ }
+ OS_FALLTHROUGH;
+ 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 == 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 = NULL;
+ 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 = NULL;
+ 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 = 0;
+ 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;
+ }
+ goto out;
+ }
+ if (tvtype == VDIR && vnode_mountedhere(tvp)) {
+ if (nd->nd_vers == NFS_VER3) {
+ error = EXDEV;
+ } else {
+ error = ENOTEMPTY;
+ }
+ goto out;
+ }
+ }
+ if (fvp == tdvp) {
+ if (nd->nd_vers == NFS_VER3) {
+ error = EINVAL;
+ } else {
+ error = ENOTEMPTY;
+ }
+ goto out;
+ }
+
+ /*
+ * Authorization.
+ *
+ * If tvp is a directory and not the same as fdvp, or tdvp is not the same as fdvp,
+ * the node is moving between directories and we need rights to remove from the
+ * old and add to the new.
+ *
+ * If tvp already exists and is not a directory, we need to be allowed to delete it.
+ *
+ * Note that we do not inherit when renaming. XXX this needs to be revisited to
+ * implement the deferred-inherit bit.
+ */
+ {
+ int moving = 0;
+
+ error = 0;
+ if ((tvp != NULL) && vnode_isdir(tvp)) {
+ if (tvp != fdvp) {
+ moving = 1;
+ }
+ } else if (tdvp != fdvp) {
+ moving = 1;
+ }
+ if (moving) {
+ /* moving out of fdvp, must have delete rights */
+ if ((error = nfsrv_authorize(fvp, fdvp, KAUTH_VNODE_DELETE, ctx, fnxo, 0)) != 0) {
+ goto auth_exit;
+ }
+ /* moving into tdvp or tvp, must have rights to add */
+ if ((error = nfsrv_authorize(((tvp != NULL) && vnode_isdir(tvp)) ? tvp : tdvp,
+ NULL,
+ vnode_isdir(fvp) ? KAUTH_VNODE_ADD_SUBDIRECTORY : KAUTH_VNODE_ADD_FILE,
+ ctx, tnxo, 0)) != 0) {
+ goto auth_exit;
+ }
+ } else {
+ /* node staying in same directory, must be allowed to add new name */
+ if ((error = nfsrv_authorize(fdvp, NULL,
+ vnode_isdir(fvp) ? KAUTH_VNODE_ADD_SUBDIRECTORY : KAUTH_VNODE_ADD_FILE,
+ ctx, fnxo, 0)) != 0) {
+ goto auth_exit;
+ }
+ }
+ /* overwriting tvp */
+ if ((tvp != NULL) && !vnode_isdir(tvp) &&
+ ((error = nfsrv_authorize(tvp, tdvp, KAUTH_VNODE_DELETE, ctx, tnxo, 0)) != 0)) {
+ goto auth_exit;
+ }
+
+ if (!error &&
+ ((error = vn_authorize_rename(fdvp, fvp, &fromni.ni_cnd, tdvp, tvp, &toni.ni_cnd, ctx, NULL)) != 0)) {
+ if (error) {
+ error = EACCES;
+ }
+ goto auth_exit;
+ }
+ /* XXX more checks? */
+
+auth_exit:
+ /* authorization denied */
+ if (error != 0) {
+ goto out;
+ }
+ }
+
+ if ((vnode_mount(fvp) != vnode_mount(tdvp)) ||
+ (tvp && (vnode_mount(fvp) != vnode_mount(tvp)))) {
+ if (nd->nd_vers == NFS_VER3) {
+ error = EXDEV;
+ } else {
+ error = ENOTEMPTY;
+ }
+ goto out;
+ }
+ /*
+ * The following edge case is caught here:
+ * (to cannot be a descendent of from)
+ *
+ * o fdvp
+ * /
+ * /
+ * o fvp
+ * \
+ * \
+ * o tdvp
+ * /
+ * /
+ * o tvp
+ */
+ if (tdvp->v_parent == fvp) {
+ if (nd->nd_vers == NFS_VER3) {
+ error = EXDEV;
+ } else {
+ error = ENOTEMPTY;
+ }
+ goto out;
+ }
+ if (fvtype == VDIR && vnode_mountedhere(fvp)) {
+ if (nd->nd_vers == NFS_VER3) {
+ error = EXDEV;
+ } else {
+ error = ENOTEMPTY;
+ }
+ goto out;
+ }
+ /*
+ * If source is the same as the destination (that is the
+ * same vnode) then there is nothing to do...
+ * EXCEPT if the underlying file system supports case
+ * insensitivity and is case preserving. In this case
+ * the file system needs to handle the special case of
+ * getting the same vnode as target (fvp) and source (tvp).
+ *
+ * Only file systems that support pathconf selectors _PC_CASE_SENSITIVE
+ * and _PC_CASE_PRESERVING can have this exception, and they need to
+ * handle the special case of getting the same vnode as target and
+ * source. NOTE: Then the target is unlocked going into vnop_rename,
+ * so not to cause locking problems. There is a single reference on tvp.
+ *
+ * NOTE - that fvp == tvp also occurs if they are hard linked - NOTE
+ * that correct behaviour then is just to remove the source (link)
+ */
+ if ((fvp == tvp) && (fdvp == tdvp)) {
+ if (fromni.ni_cnd.cn_namelen == toni.ni_cnd.cn_namelen &&
+ !bcmp(fromni.ni_cnd.cn_nameptr, toni.ni_cnd.cn_nameptr,
+ fromni.ni_cnd.cn_namelen)) {
+ goto out;
+ }
+ }
+
+ if (holding_mntlock && vnode_mount(fvp) != locked_mp) {
+ /*
+ * we're holding a reference and lock
+ * on locked_mp, but it no longer matches
+ * what we want to do... so drop our hold
+ */
+ mount_unlock_renames(locked_mp);
+ mount_drop(locked_mp, 0);
+ holding_mntlock = 0;
+ }
+ if (tdvp != fdvp && fvtype == VDIR) {
+ /*
+ * serialize renames that re-shape
+ * the tree... if holding_mntlock is
+ * set, then we're ready to go...
+ * otherwise we
+ * first need to drop the iocounts
+ * we picked up, second take the
+ * lock to serialize the access,
+ * then finally start the lookup
+ * process over with the lock held
+ */
+ if (!holding_mntlock) {
+ /*
+ * need to grab a reference on
+ * the mount point before we
+ * drop all the iocounts... once
+ * the iocounts are gone, the mount
+ * could follow
+ */
+ locked_mp = vnode_mount(fvp);
+ mount_ref(locked_mp, 0);
+
+ /* make a copy of to path to pass to nfsrv_namei() again */
+ topath = zalloc(ZV_NAMEI);
+ bcopy(toni.ni_cnd.cn_pnbuf, topath, tolen + 1);
+
+ /*
+ * nameidone has to happen before we vnode_put(tdvp)
+ * since it may need to release the fs_nodelock on the tdvp
+ */
+ nameidone(&toni);
+
+ if (tvp) {
+ vnode_put(tvp);
+ }
+ vnode_put(tdvp);
+
+ /* make a copy of from path to pass to nfsrv_namei() again */
+ frompath = zalloc(ZV_NAMEI);
+ bcopy(fromni.ni_cnd.cn_pnbuf, frompath, fromlen + 1);
+
+ /*
+ * nameidone has to happen before we vnode_put(fdvp)
+ * since it may need to release the fs_nodelock on the fdvp
+ */
+ nameidone(&fromni);
+
+ vnode_put(fvp);
+ vnode_put(fdvp);
+
+ if (fdirp) {
+ vnode_put(fdirp);
+ fdirp = NULL;
+ }
+ if (tdirp) {
+ vnode_put(tdirp);
+ tdirp = NULL;
+ }
+ mount_lock_renames(locked_mp);
+ holding_mntlock = 1;
+
+ fvp = tvp = NULL;
+ fdvp = tdvp = NULL;
+
+ fdpreattrerr = tdpreattrerr = ENOENT;
+
+ if (!topath || !frompath) {
+ /* we couldn't allocate a path, so bail */
+ error = ENOMEM;
+ goto out;
+ }
+
+ /* 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;
+ }
+
+ goto retry;
+ }
+ } else {
+ /*
+ * when we dropped the iocounts to take
+ * the lock, we allowed the identity of
+ * the various vnodes to change... if they did,
+ * we may no longer be dealing with a rename
+ * that reshapes the tree... once we're holding
+ * the iocounts, the vnodes can't change type
+ * so we're free to drop the lock at this point
+ * and continue on
+ */
+ if (holding_mntlock) {
+ mount_unlock_renames(locked_mp);
+ mount_drop(locked_mp, 0);
+ holding_mntlock = 0;
+ }
+ }
+
+ // save these off so we can later verify that fvp is the same
+ vnode_t oparent;
+ oname = fvp->v_name;
+ oparent = fvp->v_parent;
+
+ /*
+ * If generating an fsevent, then
+ * stash any pre-rename info we may need.
+ */
+#if CONFIG_FSE
+ if (nfsrv_fsevents_enabled && need_fsevent(FSE_RENAME, fvp)) {
+ int from_truncated = 0, to_truncated = 0;
+
+ get_fse_info(fvp, &from_finfo, ctx);
+ if (tvp) {
+ get_fse_info(tvp, &to_finfo, ctx);
+ }
+
+ from_name = get_pathbuff();
+ if (from_name) {
+ from_len = safe_getpath(fdvp, fromni.ni_cnd.cn_nameptr, from_name, MAXPATHLEN, &from_truncated);
+ }
+
+ to_name = from_name ? get_pathbuff() : NULL;
+ if (to_name) {
+ to_len = safe_getpath(tdvp, toni.ni_cnd.cn_nameptr, to_name, MAXPATHLEN, &to_truncated);
+ }
+
+ if (from_truncated || to_truncated) {
+ from_finfo.mode |= FSE_TRUNCATED_PATH;
+ }
+ } else {
+ from_name = NULL;
+ to_name = NULL;
+ }
+#else /* CONFIG_FSE */
+ from_name = NULL;
+ to_name = NULL;
+#endif /* CONFIG_FSE */
+
+ error = VNOP_RENAME(fromni.ni_dvp, fromni.ni_vp, &fromni.ni_cnd,
+ toni.ni_dvp, toni.ni_vp, &toni.ni_cnd, ctx);
+ /*
+ * fix up name & parent pointers. note that we first
+ * check that fvp has the same name/parent pointers it
+ * had before the rename call... this is a 'weak' check
+ * at best...
+ */
+ if (oname == fvp->v_name && oparent == fvp->v_parent) {
+ int update_flags;
+ update_flags = VNODE_UPDATE_NAME;
+ if (fdvp != tdvp) {
+ update_flags |= VNODE_UPDATE_PARENT;
+ }
+ vnode_update_identity(fvp, tdvp, toni.ni_cnd.cn_nameptr,
+ toni.ni_cnd.cn_namelen, toni.ni_cnd.cn_hash, update_flags);
+ }
+
+ /*
+ * If the rename is OK and we've got the paths
+ * then add an fsevent.
+ */
+#if CONFIG_FSE
+ if (nfsrv_fsevents_enabled && !error && from_name && to_name) {
+ if (tvp) {
+ add_fsevent(FSE_RENAME, ctx,
+ FSE_ARG_STRING, from_len, from_name,
+ FSE_ARG_FINFO, &from_finfo,
+ FSE_ARG_STRING, to_len, to_name,
+ FSE_ARG_FINFO, &to_finfo,
+ FSE_ARG_DONE);
+ } else {
+ add_fsevent(FSE_RENAME, ctx,
+ FSE_ARG_STRING, from_len, from_name,
+ FSE_ARG_FINFO, &from_finfo,
+ FSE_ARG_STRING, to_len, to_name,
+ FSE_ARG_DONE);
+ }
+ }
+ if (from_name) {
+ release_pathbuff(from_name);
+ }
+ if (to_name) {
+ release_pathbuff(to_name);
+ }
+#endif /* CONFIG_FSE */
+ from_name = to_name = NULL;
+
+out:
+ if (holding_mntlock) {
+ mount_unlock_renames(locked_mp);
+ mount_drop(locked_mp, 0);
+ holding_mntlock = 0;
+ }
+ if (tdvp) {
+ /*
+ * nameidone has to happen before we vnode_put(tdvp)
+ * since it may need to release the fs_nodelock on the tdvp
+ */
+ nameidone(&toni);
+ if (tvp) {
+ vnode_put(tvp);
+ }
+ vnode_put(tdvp);
+
+ tdvp = NULL;
+ }
+ if (fdvp) {
+ /*
+ * nameidone has to happen before we vnode_put(fdvp)
+ * since it may need to release the fs_nodelock on the fdvp
+ */
+ nameidone(&fromni);
+
+ if (fvp) {
+ vnode_put(fvp);
+ }
+ vnode_put(fdvp);
+
+ fdvp = NULL;
+ }
+ if (fdirp) {
+ nfsm_srv_vattr_init(&fdpostattr, nd->nd_vers);
+ fdpostattrerr = vnode_getattr(fdirp, &fdpostattr, ctx);
+ vnode_put(fdirp);
+ fdirp = NULL;
+ }
+ if (tdirp) {
+ nfsm_srv_vattr_init(&tdpostattr, nd->nd_vers);
+ tdpostattrerr = vnode_getattr(tdirp, &tdpostattr, ctx);
+ vnode_put(tdirp);
+ tdirp = NULL;
+ }
+
+nfsmerr:
+ /* assemble reply */
+ nd->nd_repstat = error;
+ error = nfsrv_rephead(nd, slp, &nmrep, 2 * 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,
+ fdpreattrerr, &fdpreattr, fdpostattrerr, &fdpostattr);
+ nfsm_chain_add_wcc_data(error, nd, &nmrep,
+ tdpreattrerr, &tdpreattr, tdpostattrerr, &tdpostattr);
+ }
+nfsmout:
+ nfsm_chain_build_done(error, &nmrep);
+ if (holding_mntlock) {
+ mount_unlock_renames(locked_mp);
+ mount_drop(locked_mp, 0);
+ }
+ if (tdvp) {
+ /*
+ * nameidone has to happen before we vnode_put(tdvp)
+ * since it may need to release the fs_nodelock on the tdvp
+ */
+ nameidone(&toni);
+
+ if (tvp) {
+ vnode_put(tvp);
+ }
+ vnode_put(tdvp);
+ }
+ if (fdvp) {
+ /*
+ * nameidone has to happen before we vnode_put(fdvp)
+ * since it may need to release the fs_nodelock on the fdvp
+ */
+ nameidone(&fromni);
+
+ if (fvp) {
+ vnode_put(fvp);
+ }
+ vnode_put(fdvp);
+ }
+ if (fdirp) {
+ vnode_put(fdirp);
+ }
+ if (tdirp) {
+ vnode_put(tdirp);
+ }
+ if (frompath) {
+ NFS_ZFREE(ZV_NAMEI, frompath);
+ }
+ if (topath) {
+ NFS_ZFREE(ZV_NAMEI, topath);
+ }
+ if (saved_cred) {
+ kauth_cred_unref(&saved_cred);
+ }
+ if (error) {
+ nfsm_chain_cleanup(&nmrep);
+ *mrepp = NULL;
+ }
+ return error;
+}
+
+/*
+ * nfs link service
+ */
+int
+nfsrv_link(
+ struct nfsrv_descript *nd,
+ struct nfsrv_sock *slp,
+ vfs_context_t ctx,
+ mbuf_t *mrepp)
+{
+ struct nameidata ni;
+ int error, dpreattrerr, dpostattrerr, attrerr;
+ uint32_t len = 0;
+ vnode_t vp, xp, dvp, dirp;
+ struct vnode_attr dpreattr, dpostattr, attr;
+ struct nfs_filehandle nfh, dnfh;
+ struct nfs_export *nx;
+ struct nfs_export_options *nxo;
+ struct nfsm_chain *nmreq, nmrep;
+
+ error = 0;
+ dpreattrerr = dpostattrerr = attrerr = ENOENT;
+ vp = xp = dvp = 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_fh_ptr(error, nmreq, nd->nd_vers, dnfh.nfh_fhp, dnfh.nfh_len);
+ nfsm_chain_get_32(error, nmreq, len);
+ nfsm_name_len_check(error, nd, len);
+ nfsmerr_if(error);
+ error = nfsrv_fhtovp(&nfh, nd, &vp, &nx, &nxo);
+ nfsmerr_if(error);
+
+ /* update export stats */
+ NFSStatAdd64(&nx->nx_stats.ops, 1);
+
+ /* update active user stats */
+ nfsrv_update_user_stat(nx, nd, kauth_cred_getuid(nd->nd_cr), 1, 0, 0);
+
+ error = nfsrv_credcheck(nd, ctx, nx, nxo);
+ nfsmerr_if(error);
+
+ /* we're not allowed to link to directories... */
+ if (vnode_vtype(vp) == VDIR) {
+ error = EPERM; /* POSIX */
+ goto out;
+ }
+
+ /* ...or to anything that kauth doesn't want us to (eg. immutable items) */
+ if ((error = nfsrv_authorize(vp, NULL, KAUTH_VNODE_LINKTARGET, ctx, nxo, 0)) != 0) {
+ goto out;
+ }
+
+ ni.ni_cnd.cn_nameiop = CREATE;
+#if CONFIG_TRIGGERS
+ ni.ni_op = OP_LINK;
+#endif
+ ni.ni_cnd.cn_flags = LOCKPARENT;
+ error = nfsm_chain_get_path_namei(nmreq, len, &ni);
+ if (!error) {
+ error = nfsrv_namei(nd, ctx, &ni, &dnfh, &dirp, &nx, &nxo);
+ }
+ 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;
+ }
+ }