+ * RPC to return a delegation for a file handle
+ */
+int
+nfs4_delegreturn_rpc(struct nfsmount *nmp, u_char *fhp, int fhlen, struct nfs_stateid *sid, int flags, thread_t thd, kauth_cred_t cred)
+{
+ int error = 0, status, numops;
+ uint64_t xid;
+ struct nfsm_chain nmreq, nmrep;
+ struct nfsreq_secinfo_args si;
+
+ NFSREQ_SECINFO_SET(&si, NULL, fhp, fhlen, NULL, 0);
+ nfsm_chain_null(&nmreq);
+ nfsm_chain_null(&nmrep);
+
+ // PUTFH, DELEGRETURN
+ numops = 2;
+ nfsm_chain_build_alloc_init(error, &nmreq, 16 * NFSX_UNSIGNED);
+ nfsm_chain_add_compound_header(error, &nmreq, "delegreturn", numops);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+ nfsm_chain_add_fh(error, &nmreq, nmp->nm_vers, fhp, fhlen);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_DELEGRETURN);
+ nfsm_chain_add_stateid(error, &nmreq, sid);
+ nfsm_chain_build_done(error, &nmreq);
+ nfsm_assert(error, (numops == 0), EPROTO);
+ nfsmout_if(error);
+ error = nfs_request2(NULL, nmp->nm_mountp, &nmreq, NFSPROC4_COMPOUND, thd, cred, &si, flags, &nmrep, &xid, &status);
+ nfsm_chain_skip_tag(error, &nmrep);
+ nfsm_chain_get_32(error, &nmrep, numops);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_DELEGRETURN);
+nfsmout:
+ nfsm_chain_cleanup(&nmreq);
+ nfsm_chain_cleanup(&nmrep);
+ return (error);
+}
+
+
+/*
+ * NFS read call.
+ * Just call nfs_bioread() to do the work.
+ *
+ * Note: the exec code paths have a tendency to call VNOP_READ (and VNOP_MMAP)
+ * without first calling VNOP_OPEN, so we make sure the file is open here.
+ */
+int
+nfs_vnop_read(
+ struct vnop_read_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_vp;
+ struct uio *a_uio;
+ int a_ioflag;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ vnode_t vp = ap->a_vp;
+ vfs_context_t ctx = ap->a_context;
+ nfsnode_t np;
+ struct nfsmount *nmp;
+ struct nfs_open_owner *noop;
+ struct nfs_open_file *nofp;
+ int error;
+
+ if (vnode_vtype(ap->a_vp) != VREG)
+ return (EPERM);
+
+ np = VTONFS(vp);
+ nmp = NFSTONMP(np);
+ if (!nmp)
+ return (ENXIO);
+ 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_open_file_find(np, noop, &nofp, 0, 0, 1);
+ if (!error && (nofp->nof_flags & NFS_OPEN_FILE_LOST)) {
+ NP(np, "nfs_vnop_read: LOST %d", kauth_cred_getuid(noop->noo_cred));
+ error = EIO;
+ }
+ if (!error && (nofp->nof_flags & NFS_OPEN_FILE_REOPEN)) {
+ error = nfs4_reopen(nofp, vfs_context_thread(ctx));
+ nofp = NULL;
+ if (!error)
+ goto restart;
+ }
+ if (error) {
+ nfs_open_owner_rele(noop);
+ return (error);
+ }
+ if (!nofp->nof_access) {
+ /* we don't have the file open, so open it for read access */
+ error = nfs_mount_state_in_use_start(nmp, vfs_context_thread(ctx));
+ if (error) {
+ nfs_open_owner_rele(noop);
+ return (error);
+ }
+ if (np->n_flag & NREVOKE) {
+ error = EIO;
+ nfs_mount_state_in_use_end(nmp, 0);
+ nfs_open_owner_rele(noop);
+ return (error);
+ }
+ error = nfs_open_file_set_busy(nofp, vfs_context_thread(ctx));
+ if (error)
+ nofp = NULL;
+ if (!error) {
+ if (nmp->nm_vers < NFS_VER4) {
+ /* NFS v2/v3 opens are always allowed - so just add it. */
+ nfs_open_file_add_open(nofp, NFS_OPEN_SHARE_ACCESS_READ, NFS_OPEN_SHARE_DENY_NONE, 0);
+ } else {
+ error = nfs4_open(np, nofp, NFS_OPEN_SHARE_ACCESS_READ, NFS_OPEN_SHARE_DENY_NONE, ctx);
+ }
+ }
+ if (!error)
+ nofp->nof_flags |= NFS_OPEN_FILE_NEEDCLOSE;
+ if (nofp)
+ nfs_open_file_clear_busy(nofp);
+ if (nfs_mount_state_in_use_end(nmp, error)) {
+ nofp = NULL;
+ goto restart;
+ }
+ }
+ nfs_open_owner_rele(noop);
+ if (error)
+ return (error);
+ return (nfs_bioread(VTONFS(ap->a_vp), ap->a_uio, ap->a_ioflag, ap->a_context));
+}
+
+/*
+ * Note: the NFSv4 CREATE RPC is for everything EXCEPT regular files.
+ * Files are created using the NFSv4 OPEN RPC. So we must open the
+ * file to create it and then close it.
+ */
+int
+nfs4_vnop_create(
+ struct vnop_create_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_dvp;
+ vnode_t *a_vpp;
+ struct componentname *a_cnp;
+ struct vnode_attr *a_vap;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ vfs_context_t ctx = ap->a_context;
+ struct componentname *cnp = ap->a_cnp;
+ struct vnode_attr *vap = ap->a_vap;
+ vnode_t dvp = ap->a_dvp;
+ vnode_t *vpp = ap->a_vpp;
+ struct nfsmount *nmp;
+ nfsnode_t np;
+ int error = 0, busyerror = 0, accessMode, denyMode;
+ struct nfs_open_owner *noop = NULL;
+ struct nfs_open_file *newnofp = NULL, *nofp = NULL;
+
+ nmp = VTONMP(dvp);
+ if (!nmp)
+ return (ENXIO);
+
+ if (vap)
+ nfs_avoid_needless_id_setting_on_create(VTONFS(dvp), vap, ctx);
+
+ 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) {
+ nfs_open_owner_rele(noop);
+ return (error);
+ }
+
+ /* grab a provisional, nodeless open file */
+ error = nfs_open_file_find(NULL, noop, &newnofp, 0, 0, 1);
+ if (!error && (newnofp->nof_flags & NFS_OPEN_FILE_LOST)) {
+ printf("nfs_vnop_create: LOST\n");
+ error = EIO;
+ }
+ if (!error && (newnofp->nof_flags & NFS_OPEN_FILE_REOPEN)) {
+ /* This shouldn't happen given that this is a new, nodeless nofp */
+ nfs_mount_state_in_use_end(nmp, 0);
+ error = nfs4_reopen(newnofp, vfs_context_thread(ctx));
+ nfs_open_file_destroy(newnofp);
+ newnofp = NULL;
+ if (!error)
+ goto restart;
+ }
+ if (!error)
+ error = nfs_open_file_set_busy(newnofp, vfs_context_thread(ctx));
+ if (error) {
+ if (newnofp)
+ nfs_open_file_destroy(newnofp);
+ newnofp = NULL;
+ goto out;
+ }
+
+ /*
+ * We're just trying to create the file.
+ * We'll create/open it RW, and set NFS_OPEN_FILE_CREATE.
+ */
+ accessMode = NFS_OPEN_SHARE_ACCESS_BOTH;
+ denyMode = NFS_OPEN_SHARE_DENY_NONE;
+
+ /* Do the open/create */
+ error = nfs4_open_rpc(newnofp, ctx, cnp, vap, dvp, vpp, NFS_OPEN_CREATE, accessMode, denyMode);
+ if ((error == EACCES) && vap && !(vap->va_vaflags & VA_EXCLUSIVE) &&
+ VATTR_IS_ACTIVE(vap, va_mode) && !(vap->va_mode & S_IWUSR)) {
+ /*
+ * Hmm... it looks like we may have a situation where the request was
+ * retransmitted because we didn't get the first response which successfully
+ * created/opened the file and then the second time we were denied the open
+ * because the mode the file was created with doesn't allow write access.
+ *
+ * We'll try to work around this by temporarily updating the mode and
+ * retrying the open.
+ */
+ struct vnode_attr vattr;
+
+ /* first make sure it's there */
+ int error2 = nfs_lookitup(VTONFS(dvp), cnp->cn_nameptr, cnp->cn_namelen, ctx, &np);
+ if (!error2 && np) {
+ nfs_node_unlock(np);
+ *vpp = NFSTOV(np);
+ if (vnode_vtype(NFSTOV(np)) == VREG) {
+ VATTR_INIT(&vattr);
+ VATTR_SET(&vattr, va_mode, (vap->va_mode | S_IWUSR));
+ if (!nfs4_setattr_rpc(np, &vattr, ctx)) {
+ error2 = nfs4_open_rpc(newnofp, ctx, cnp, NULL, dvp, vpp, NFS_OPEN_NOCREATE, accessMode, denyMode);
+ VATTR_INIT(&vattr);
+ VATTR_SET(&vattr, va_mode, vap->va_mode);
+ nfs4_setattr_rpc(np, &vattr, ctx);
+ if (!error2)
+ error = 0;
+ }
+ }
+ if (error) {
+ vnode_put(*vpp);
+ *vpp = NULL;
+ }
+ }
+ }
+ if (!error && !*vpp) {
+ printf("nfs4_open_rpc returned without a node?\n");
+ /* Hmmm... with no node, we have no filehandle and can't close it */
+ error = EIO;
+ }
+ if (error) {
+ /* need to cleanup our temporary nofp */
+ nfs_open_file_clear_busy(newnofp);
+ nfs_open_file_destroy(newnofp);
+ newnofp = NULL;
+ goto out;
+ }
+ /* After we have a node, add our open file struct to the node */
+ np = VTONFS(*vpp);
+ nfs_open_file_add_open(newnofp, accessMode, denyMode, 0);
+ nofp = newnofp;
+ error = nfs_open_file_find_internal(np, noop, &nofp, 0, 0, 0);
+ if (error) {
+ /* This shouldn't happen, because we passed in a new nofp to use. */
+ printf("nfs_open_file_find_internal failed! %d\n", error);
+ goto out;
+ } else if (nofp != newnofp) {
+ /*
+ * Hmm... an open file struct already exists.
+ * Mark the existing one busy and merge our open into it.
+ * Then destroy the one we created.
+ * Note: there's no chance of an open confict because the
+ * open has already been granted.
+ */
+ busyerror = nfs_open_file_set_busy(nofp, NULL);
+ nfs_open_file_add_open(nofp, accessMode, denyMode, 0);
+ nofp->nof_stateid = newnofp->nof_stateid;
+ if (newnofp->nof_flags & NFS_OPEN_FILE_POSIXLOCK)
+ nofp->nof_flags |= NFS_OPEN_FILE_POSIXLOCK;
+ nfs_open_file_clear_busy(newnofp);
+ nfs_open_file_destroy(newnofp);
+ }
+ newnofp = NULL;
+ /* mark the node as holding a create-initiated open */
+ nofp->nof_flags |= NFS_OPEN_FILE_CREATE;
+ nofp->nof_creator = current_thread();
+out:
+ if (nofp && !busyerror)
+ nfs_open_file_clear_busy(nofp);
+ if (nfs_mount_state_in_use_end(nmp, error)) {
+ nofp = newnofp = NULL;
+ busyerror = 0;
+ goto restart;
+ }
+ if (noop)
+ nfs_open_owner_rele(noop);
+ return (error);
+}
+
+/*
+ * Note: the NFSv4 CREATE RPC is for everything EXCEPT regular files.
+ */
+int
+nfs4_create_rpc(
+ vfs_context_t ctx,
+ nfsnode_t dnp,
+ struct componentname *cnp,
+ struct vnode_attr *vap,
+ int type,
+ char *link,
+ nfsnode_t *npp)
+{
+ struct nfsmount *nmp;
+ struct nfs_vattr nvattr;
+ int error = 0, create_error = EIO, lockerror = ENOENT, busyerror = ENOENT, status;
+ int nfsvers, namedattrs, numops;
+ u_int64_t xid, savedxid = 0;
+ nfsnode_t np = NULL;
+ vnode_t newvp = NULL;
+ struct nfsm_chain nmreq, nmrep;
+ uint32_t bitmap[NFS_ATTR_BITMAP_LEN], bmlen;
+ const char *tag;
+ nfs_specdata sd;
+ fhandle_t fh;
+ struct nfsreq rq, *req = &rq;
+ struct nfs_dulookup dul;
+ struct nfsreq_secinfo_args si;
+
+ nmp = NFSTONMP(dnp);
+ if (!nmp)
+ return (ENXIO);
+ nfsvers = nmp->nm_vers;
+ namedattrs = (nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_NAMED_ATTR);
+ if (dnp->n_vattr.nva_flags & NFS_FFLAG_TRIGGER_REFERRAL)
+ return (EINVAL);
+
+ sd.specdata1 = sd.specdata2 = 0;
+
+ switch (type) {
+ case NFLNK:
+ tag = "symlink";
+ break;
+ case NFBLK:
+ case NFCHR:
+ tag = "mknod";
+ if (!VATTR_IS_ACTIVE(vap, va_rdev))
+ return (EINVAL);
+ sd.specdata1 = major(vap->va_rdev);
+ sd.specdata2 = minor(vap->va_rdev);
+ break;
+ case NFSOCK:
+ case NFFIFO:
+ tag = "mknod";
+ break;
+ case NFDIR:
+ tag = "mkdir";
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ nfs_avoid_needless_id_setting_on_create(dnp, vap, ctx);
+
+ error = busyerror = nfs_node_set_busy(dnp, vfs_context_thread(ctx));
+ if (!namedattrs)
+ nfs_dulookup_init(&dul, dnp, cnp->cn_nameptr, cnp->cn_namelen, ctx);
+
+ NFSREQ_SECINFO_SET(&si, dnp, NULL, 0, NULL, 0);
+ NVATTR_INIT(&nvattr);
+ nfsm_chain_null(&nmreq);
+ nfsm_chain_null(&nmrep);
+
+ // PUTFH, SAVEFH, CREATE, GETATTR(FH), RESTOREFH, GETATTR
+ numops = 6;
+ nfsm_chain_build_alloc_init(error, &nmreq, 66 * NFSX_UNSIGNED);
+ nfsm_chain_add_compound_header(error, &nmreq, tag, numops);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+ nfsm_chain_add_fh(error, &nmreq, nfsvers, dnp->n_fhp, dnp->n_fhsize);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_SAVEFH);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_CREATE);
+ nfsm_chain_add_32(error, &nmreq, type);
+ if (type == NFLNK) {
+ nfsm_chain_add_name(error, &nmreq, link, strlen(link), nmp);
+ } else if ((type == NFBLK) || (type == NFCHR)) {
+ nfsm_chain_add_32(error, &nmreq, sd.specdata1);
+ nfsm_chain_add_32(error, &nmreq, sd.specdata2);
+ }
+ nfsm_chain_add_name(error, &nmreq, cnp->cn_nameptr, cnp->cn_namelen, nmp);
+ nfsm_chain_add_fattr4(error, &nmreq, vap, nmp);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+ NFS_COPY_ATTRIBUTES(nfs_getattr_bitmap, bitmap);
+ NFS_BITMAP_SET(bitmap, NFS_FATTR_FILEHANDLE);
+ nfsm_chain_add_bitmap_supported(error, &nmreq, bitmap, nmp, NULL);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_RESTOREFH);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+ nfsm_chain_add_bitmap_supported(error, &nmreq, nfs_getattr_bitmap, nmp, dnp);
+ nfsm_chain_build_done(error, &nmreq);
+ nfsm_assert(error, (numops == 0), EPROTO);
+ nfsmout_if(error);
+
+ error = nfs_request_async(dnp, NULL, &nmreq, NFSPROC4_COMPOUND,
+ vfs_context_thread(ctx), vfs_context_ucred(ctx), &si, 0, NULL, &req);
+ if (!error) {
+ if (!namedattrs)
+ nfs_dulookup_start(&dul, dnp, ctx);
+ error = nfs_request_async_finish(req, &nmrep, &xid, &status);
+ }
+
+ if ((lockerror = nfs_node_lock(dnp)))
+ error = lockerror;
+ nfsm_chain_skip_tag(error, &nmrep);
+ nfsm_chain_get_32(error, &nmrep, numops);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_SAVEFH);
+ nfsmout_if(error);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_CREATE);
+ nfsm_chain_check_change_info(error, &nmrep, dnp);
+ bmlen = NFS_ATTR_BITMAP_LEN;
+ nfsm_chain_get_bitmap(error, &nmrep, bitmap, bmlen);
+ /* At this point if we have no error, the object was created. */
+ /* if we don't get attributes, then we should lookitup. */
+ create_error = error;
+ nfsmout_if(error);
+ nfs_vattr_set_supported(bitmap, vap);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+ nfsmout_if(error);
+ error = nfs4_parsefattr(&nmrep, NULL, &nvattr, &fh, NULL, NULL);
+ nfsmout_if(error);
+ if (!NFS_BITMAP_ISSET(nvattr.nva_bitmap, NFS_FATTR_FILEHANDLE)) {
+ printf("nfs: create/%s didn't return filehandle? %s\n", tag, cnp->cn_nameptr);
+ error = EBADRPC;
+ goto nfsmout;
+ }
+ /* directory attributes: if we don't get them, make sure to invalidate */
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_RESTOREFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+ savedxid = xid;
+ nfsm_chain_loadattr(error, &nmrep, dnp, nfsvers, &xid);
+ if (error)
+ NATTRINVALIDATE(dnp);
+
+nfsmout:
+ nfsm_chain_cleanup(&nmreq);
+ nfsm_chain_cleanup(&nmrep);
+
+ if (!lockerror) {
+ if (!create_error && (dnp->n_flag & NNEGNCENTRIES)) {
+ dnp->n_flag &= ~NNEGNCENTRIES;
+ cache_purge_negatives(NFSTOV(dnp));
+ }
+ dnp->n_flag |= NMODIFIED;
+ nfs_node_unlock(dnp);
+ /* nfs_getattr() will check changed and purge caches */
+ nfs_getattr(dnp, NULL, ctx, NGA_CACHED);
+ }
+
+ if (!error && fh.fh_len) {
+ /* create the vnode with the filehandle and attributes */
+ xid = savedxid;
+ error = nfs_nget(NFSTOMP(dnp), dnp, cnp, fh.fh_data, fh.fh_len, &nvattr, &xid, rq.r_auth, NG_MAKEENTRY, &np);
+ if (!error)
+ newvp = NFSTOV(np);
+ }
+ NVATTR_CLEANUP(&nvattr);
+
+ if (!namedattrs)
+ nfs_dulookup_finish(&dul, dnp, ctx);
+
+ /*
+ * Kludge: Map EEXIST => 0 assuming that you have a reply to a retry
+ * if we can succeed in looking up the object.
+ */
+ if ((create_error == EEXIST) || (!create_error && !newvp)) {
+ error = nfs_lookitup(dnp, cnp->cn_nameptr, cnp->cn_namelen, ctx, &np);
+ if (!error) {
+ newvp = NFSTOV(np);
+ if (vnode_vtype(newvp) != nfstov_type(type, nfsvers))
+ error = EEXIST;
+ }
+ }
+ if (!busyerror)
+ nfs_node_clear_busy(dnp);
+ if (error) {
+ if (newvp) {
+ nfs_node_unlock(np);
+ vnode_put(newvp);
+ }
+ } else {
+ nfs_node_unlock(np);
+ *npp = np;
+ }
+ return (error);
+}
+
+int
+nfs4_vnop_mknod(
+ struct vnop_mknod_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_dvp;
+ vnode_t *a_vpp;
+ struct componentname *a_cnp;
+ struct vnode_attr *a_vap;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ nfsnode_t np = NULL;
+ struct nfsmount *nmp;
+ int error;
+
+ nmp = VTONMP(ap->a_dvp);
+ if (!nmp)
+ return (ENXIO);
+
+ if (!VATTR_IS_ACTIVE(ap->a_vap, va_type))
+ return (EINVAL);
+ switch (ap->a_vap->va_type) {
+ case VBLK:
+ case VCHR:
+ case VFIFO:
+ case VSOCK:
+ break;
+ default:
+ return (ENOTSUP);
+ }
+
+ error = nfs4_create_rpc(ap->a_context, VTONFS(ap->a_dvp), ap->a_cnp, ap->a_vap,
+ vtonfs_type(ap->a_vap->va_type, nmp->nm_vers), NULL, &np);
+ if (!error)
+ *ap->a_vpp = NFSTOV(np);
+ return (error);
+}
+
+int
+nfs4_vnop_mkdir(
+ struct vnop_mkdir_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_dvp;
+ vnode_t *a_vpp;
+ struct componentname *a_cnp;
+ struct vnode_attr *a_vap;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ nfsnode_t np = NULL;
+ int error;
+
+ error = nfs4_create_rpc(ap->a_context, VTONFS(ap->a_dvp), ap->a_cnp, ap->a_vap,
+ NFDIR, NULL, &np);
+ if (!error)
+ *ap->a_vpp = NFSTOV(np);
+ return (error);
+}
+
+int
+nfs4_vnop_symlink(
+ struct vnop_symlink_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_dvp;
+ vnode_t *a_vpp;
+ struct componentname *a_cnp;
+ struct vnode_attr *a_vap;
+ char *a_target;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ nfsnode_t np = NULL;
+ int error;
+
+ error = nfs4_create_rpc(ap->a_context, VTONFS(ap->a_dvp), ap->a_cnp, ap->a_vap,
+ NFLNK, ap->a_target, &np);
+ if (!error)
+ *ap->a_vpp = NFSTOV(np);
+ return (error);
+}
+
+int
+nfs4_vnop_link(
+ struct vnop_link_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_vp;
+ vnode_t a_tdvp;
+ struct componentname *a_cnp;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ vfs_context_t ctx = ap->a_context;
+ vnode_t vp = ap->a_vp;
+ vnode_t tdvp = ap->a_tdvp;
+ struct componentname *cnp = ap->a_cnp;
+ int error = 0, lockerror = ENOENT, status;
+ struct nfsmount *nmp;
+ nfsnode_t np = VTONFS(vp);
+ nfsnode_t tdnp = VTONFS(tdvp);
+ int nfsvers, numops;
+ u_int64_t xid, savedxid;
+ struct nfsm_chain nmreq, nmrep;
+ struct nfsreq_secinfo_args si;
+
+ if (vnode_mount(vp) != vnode_mount(tdvp))
+ return (EXDEV);
+
+ nmp = VTONMP(vp);
+ if (!nmp)
+ return (ENXIO);
+ nfsvers = nmp->nm_vers;
+ if (np->n_vattr.nva_flags & NFS_FFLAG_TRIGGER_REFERRAL)
+ return (EINVAL);
+ if (tdnp->n_vattr.nva_flags & NFS_FFLAG_TRIGGER_REFERRAL)
+ return (EINVAL);
+
+ /*
+ * Push all writes to the server, so that the attribute cache
+ * doesn't get "out of sync" with the server.
+ * XXX There should be a better way!
+ */
+ nfs_flush(np, MNT_WAIT, vfs_context_thread(ctx), V_IGNORE_WRITEERR);
+
+ if ((error = nfs_node_set_busy2(tdnp, np, vfs_context_thread(ctx))))
+ return (error);
+
+ NFSREQ_SECINFO_SET(&si, np, NULL, 0, NULL, 0);
+ nfsm_chain_null(&nmreq);
+ nfsm_chain_null(&nmrep);
+
+ // PUTFH(SOURCE), SAVEFH, PUTFH(DIR), LINK, GETATTR(DIR), RESTOREFH, GETATTR
+ numops = 7;
+ nfsm_chain_build_alloc_init(error, &nmreq, 29 * NFSX_UNSIGNED + cnp->cn_namelen);
+ nfsm_chain_add_compound_header(error, &nmreq, "link", numops);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+ nfsm_chain_add_fh(error, &nmreq, nfsvers, np->n_fhp, np->n_fhsize);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_SAVEFH);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_PUTFH);
+ nfsm_chain_add_fh(error, &nmreq, nfsvers, tdnp->n_fhp, tdnp->n_fhsize);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_LINK);
+ nfsm_chain_add_name(error, &nmreq, cnp->cn_nameptr, cnp->cn_namelen, nmp);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+ nfsm_chain_add_bitmap_supported(error, &nmreq, nfs_getattr_bitmap, nmp, tdnp);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_RESTOREFH);
+ numops--;
+ nfsm_chain_add_32(error, &nmreq, NFS_OP_GETATTR);
+ nfsm_chain_add_bitmap_supported(error, &nmreq, nfs_getattr_bitmap, nmp, np);
+ nfsm_chain_build_done(error, &nmreq);
+ nfsm_assert(error, (numops == 0), EPROTO);
+ nfsmout_if(error);
+ error = nfs_request(tdnp, NULL, &nmreq, NFSPROC4_COMPOUND, ctx, &si, &nmrep, &xid, &status);
+
+ if ((lockerror = nfs_node_lock2(tdnp, np))) {
+ error = lockerror;
+ goto nfsmout;
+ }
+ nfsm_chain_skip_tag(error, &nmrep);
+ nfsm_chain_get_32(error, &nmrep, numops);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_SAVEFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_PUTFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_LINK);
+ nfsm_chain_check_change_info(error, &nmrep, tdnp);
+ /* directory attributes: if we don't get them, make sure to invalidate */
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+ savedxid = xid;
+ nfsm_chain_loadattr(error, &nmrep, tdnp, nfsvers, &xid);
+ if (error)
+ NATTRINVALIDATE(tdnp);
+ /* link attributes: if we don't get them, make sure to invalidate */
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_RESTOREFH);
+ nfsm_chain_op_check(error, &nmrep, NFS_OP_GETATTR);
+ xid = savedxid;
+ nfsm_chain_loadattr(error, &nmrep, np, nfsvers, &xid);
+ if (error)
+ NATTRINVALIDATE(np);
+nfsmout:
+ nfsm_chain_cleanup(&nmreq);
+ nfsm_chain_cleanup(&nmrep);
+ if (!lockerror)
+ tdnp->n_flag |= NMODIFIED;
+ /* Kludge: Map EEXIST => 0 assuming that it is a reply to a retry. */
+ if (error == EEXIST)
+ error = 0;
+ if (!error && (tdnp->n_flag & NNEGNCENTRIES)) {
+ tdnp->n_flag &= ~NNEGNCENTRIES;
+ cache_purge_negatives(tdvp);
+ }
+ if (!lockerror)
+ nfs_node_unlock2(tdnp, np);
+ nfs_node_clear_busy2(tdnp, np);
+ return (error);
+}
+
+int
+nfs4_vnop_rmdir(
+ struct vnop_rmdir_args /* {
+ struct vnodeop_desc *a_desc;
+ vnode_t a_dvp;
+ vnode_t a_vp;
+ struct componentname *a_cnp;
+ vfs_context_t a_context;
+ } */ *ap)
+{
+ vfs_context_t ctx = ap->a_context;
+ vnode_t vp = ap->a_vp;
+ vnode_t dvp = ap->a_dvp;
+ struct componentname *cnp = ap->a_cnp;
+ struct nfsmount *nmp;
+ int error = 0, namedattrs;
+ nfsnode_t np = VTONFS(vp);
+ nfsnode_t dnp = VTONFS(dvp);
+ struct nfs_dulookup dul;
+
+ if (vnode_vtype(vp) != VDIR)
+ return (EINVAL);
+
+ nmp = NFSTONMP(dnp);
+ if (!nmp)
+ return (ENXIO);
+ namedattrs = (nmp->nm_fsattr.nfsa_flags & NFS_FSFLAG_NAMED_ATTR);
+
+ if ((error = nfs_node_set_busy2(dnp, np, vfs_context_thread(ctx))))
+ return (error);
+
+ if (!namedattrs) {
+ nfs_dulookup_init(&dul, dnp, cnp->cn_nameptr, cnp->cn_namelen, ctx);
+ nfs_dulookup_start(&dul, dnp, ctx);
+ }
+
+ error = nfs4_remove_rpc(dnp, cnp->cn_nameptr, cnp->cn_namelen,
+ vfs_context_thread(ctx), vfs_context_ucred(ctx));
+
+ nfs_name_cache_purge(dnp, np, cnp, ctx);
+ /* nfs_getattr() will check changed and purge caches */
+ nfs_getattr(dnp, NULL, ctx, NGA_CACHED);
+ if (!namedattrs)
+ nfs_dulookup_finish(&dul, dnp, ctx);
+ nfs_node_clear_busy2(dnp, np);
+
+ /*
+ * Kludge: Map ENOENT => 0 assuming that you have a reply to a retry.
+ */
+ if (error == ENOENT)
+ error = 0;
+ if (!error) {
+ /*
+ * remove nfsnode from hash now so we can't accidentally find it
+ * again if another object gets created with the same filehandle
+ * before this vnode gets reclaimed
+ */
+ lck_mtx_lock(nfs_node_hash_mutex);
+ if (np->n_hflag & NHHASHED) {
+ LIST_REMOVE(np, n_hash);
+ np->n_hflag &= ~NHHASHED;
+ FSDBG(266, 0, np, np->n_flag, 0xb1eb1e);
+ }
+ lck_mtx_unlock(nfs_node_hash_mutex);
+ }
+ return (error);
+}
+
+/*
+ * NFSv4 Named Attributes
+ *
+ * Both the extended attributes interface and the named streams interface
+ * are backed by NFSv4 named attributes. The implementations for both use
+ * a common set of routines in an attempt to reduce code duplication, to
+ * increase efficiency, to increase caching of both names and data, and to
+ * confine the complexity.
+ *
+ * Each NFS node caches its named attribute directory's file handle.
+ * The directory nodes for the named attribute directories are handled
+ * exactly like regular directories (with a couple minor exceptions).
+ * Named attribute nodes are also treated as much like regular files as
+ * possible.
+ *
+ * Most of the heavy lifting is done by nfs4_named_attr_get().
+ */
+
+/*
+ * Get the given node's attribute directory node.
+ * If !fetch, then only return a cached node.
+ * Otherwise, we will attempt to fetch the node from the server.
+ * (Note: the node should be marked busy.)