+/*
+ * Build the RPC header and fill in the authorization info.
+ * Returns the head of the mbuf list and the xid.
+ */
+
+int
+nfsm_rpchead(
+ struct nfsreq *req,
+ mbuf_t mrest,
+ u_int64_t *xidp,
+ mbuf_t *mreqp)
+{
+ struct nfsmount *nmp = req->r_nmp;
+ int nfsvers = nmp->nm_vers;
+ int proc = ((nfsvers == NFS_VER2) ? nfsv2_procid[req->r_procnum] : (int)req->r_procnum);
+
+ return nfsm_rpchead2(nmp, nmp->nm_sotype, NFS_PROG, nfsvers, proc,
+ req->r_auth, req->r_cred, req, mrest, xidp, mreqp);
+}
+
+int
+nfsm_rpchead2(struct nfsmount *nmp, int sotype, int prog, int vers, int proc, int auth_type,
+ kauth_cred_t cred, struct nfsreq *req, mbuf_t mrest, u_int64_t *xidp, mbuf_t *mreqp)
+{
+ mbuf_t mreq, mb;
+ int error, i, grpsiz, auth_len = 0, authsiz, reqlen;
+ size_t headlen;
+ struct nfsm_chain nmreq;
+
+ /* calculate expected auth length */
+ switch (auth_type) {
+ case RPCAUTH_NONE:
+ auth_len = 0;
+ break;
+ case RPCAUTH_SYS:
+ {
+ gid_t grouplist[NGROUPS];
+ int groupcount = NGROUPS;
+
+ if (!cred)
+ return (EINVAL);
+
+ (void)kauth_cred_getgroups(cred, grouplist, &groupcount);
+ if (groupcount < 1)
+ return (EINVAL);
+
+ auth_len = ((((groupcount - 1) > nmp->nm_numgrps) ?
+ nmp->nm_numgrps : (groupcount - 1)) << 2) +
+ 5 * NFSX_UNSIGNED;
+ break;
+ }
+ case RPCAUTH_KRB5:
+ case RPCAUTH_KRB5I:
+ case RPCAUTH_KRB5P:
+ if (!req || !cred)
+ return (EINVAL);
+ auth_len = 5 * NFSX_UNSIGNED + 0; // zero context handle for now
+ break;
+ default:
+ return (EINVAL);
+ }
+ authsiz = nfsm_rndup(auth_len);
+
+ /* allocate the packet */
+ headlen = authsiz + 10 * NFSX_UNSIGNED;
+ if (sotype == SOCK_STREAM) /* also include room for any RPC Record Mark */
+ headlen += NFSX_UNSIGNED;
+ if (headlen >= nfs_mbuf_minclsize) {
+ error = mbuf_getpacket(MBUF_WAITOK, &mreq);
+ } else {
+ error = mbuf_gethdr(MBUF_WAITOK, MBUF_TYPE_DATA, &mreq);
+ if (!error) {
+ if (headlen < nfs_mbuf_mhlen)
+ mbuf_align_32(mreq, headlen);
+ else
+ mbuf_align_32(mreq, 8 * NFSX_UNSIGNED);
+ }
+ }
+ if (error) {
+ /* unable to allocate packet */
+ /* XXX should we keep statistics for these errors? */
+ return (error);
+ }
+
+ /*
+ * If the caller gave us a non-zero XID then use it because
+ * it may be a higher-level resend with a GSSAPI credential.
+ * Otherwise, allocate a new one.
+ */
+ if (*xidp == 0)
+ nfs_get_xid(xidp);
+
+ /* build the header(s) */
+ nfsm_chain_init(&nmreq, mreq);
+
+ /* First, if it's a TCP stream insert space for an RPC record mark */
+ if (sotype == SOCK_STREAM)
+ nfsm_chain_add_32(error, &nmreq, 0);
+
+ /* Then the RPC header. */
+ nfsm_chain_add_32(error, &nmreq, (*xidp & 0xffffffff));
+ nfsm_chain_add_32(error, &nmreq, RPC_CALL);
+ nfsm_chain_add_32(error, &nmreq, RPC_VER2);
+ nfsm_chain_add_32(error, &nmreq, prog);
+ nfsm_chain_add_32(error, &nmreq, vers);
+ nfsm_chain_add_32(error, &nmreq, proc);
+
+add_cred:
+ switch (auth_type) {
+ case RPCAUTH_NONE:
+ nfsm_chain_add_32(error, &nmreq, RPCAUTH_NONE); /* auth */
+ nfsm_chain_add_32(error, &nmreq, 0); /* length */
+ nfsm_chain_add_32(error, &nmreq, RPCAUTH_NONE); /* verf */
+ nfsm_chain_add_32(error, &nmreq, 0); /* length */
+ nfsm_chain_build_done(error, &nmreq);
+ /* Append the args mbufs */
+ if (!error)
+ error = mbuf_setnext(nmreq.nmc_mcur, mrest);
+ break;
+ case RPCAUTH_SYS: {
+ gid_t grouplist[NGROUPS];
+ int groupcount;
+
+ nfsm_chain_add_32(error, &nmreq, RPCAUTH_SYS);
+ nfsm_chain_add_32(error, &nmreq, authsiz);
+ nfsm_chain_add_32(error, &nmreq, 0); /* stamp */
+ nfsm_chain_add_32(error, &nmreq, 0); /* zero-length hostname */
+ nfsm_chain_add_32(error, &nmreq, kauth_cred_getuid(cred)); /* UID */
+ nfsm_chain_add_32(error, &nmreq, kauth_cred_getgid(cred)); /* GID */
+ grpsiz = (auth_len >> 2) - 5;
+ nfsm_chain_add_32(error, &nmreq, grpsiz);/* additional GIDs */
+ memset(grouplist, 0, sizeof(grouplist));
+ groupcount = grpsiz;
+ (void)kauth_cred_getgroups(cred, grouplist, &groupcount);
+ for (i = 1; i <= grpsiz; i++)
+ nfsm_chain_add_32(error, &nmreq, grouplist[i]);
+
+ /* And the verifier... */
+ nfsm_chain_add_32(error, &nmreq, RPCAUTH_NONE); /* flavor */
+ nfsm_chain_add_32(error, &nmreq, 0); /* length */
+ nfsm_chain_build_done(error, &nmreq);
+
+ /* Append the args mbufs */
+ if (!error)
+ error = mbuf_setnext(nmreq.nmc_mcur, mrest);
+ break;
+ }
+ case RPCAUTH_KRB5:
+ case RPCAUTH_KRB5I:
+ case RPCAUTH_KRB5P:
+ error = nfs_gss_clnt_cred_put(req, &nmreq, mrest);
+ if (error == ENEEDAUTH) {
+ gid_t grouplist[NGROUPS];
+ int groupcount = NGROUPS;
+ /*
+ * Use sec=sys for this user
+ */
+ error = 0;
+ req->r_auth = auth_type = RPCAUTH_SYS;
+ (void)kauth_cred_getgroups(cred, grouplist, &groupcount);
+ auth_len = ((((groupcount - 1) > nmp->nm_numgrps) ?
+ nmp->nm_numgrps : (groupcount - 1)) << 2) +
+ 5 * NFSX_UNSIGNED;
+ authsiz = nfsm_rndup(auth_len);
+ goto add_cred;
+ }
+ break;
+ };
+
+ /* finish setting up the packet */
+ if (!error)
+ error = mbuf_pkthdr_setrcvif(mreq, 0);
+
+ if (error) {
+ mbuf_freem(mreq);
+ return (error);
+ }
+
+ /* Calculate the size of the request */
+ reqlen = 0;
+ for (mb = nmreq.nmc_mhead; mb; mb = mbuf_next(mb))
+ reqlen += mbuf_len(mb);
+
+ mbuf_pkthdr_setlen(mreq, reqlen);
+
+ /*
+ * If the request goes on a TCP stream,
+ * set its size in the RPC record mark.
+ * The record mark count doesn't include itself
+ * and the last fragment bit is set.
+ */
+ if (sotype == SOCK_STREAM)
+ nfsm_chain_set_recmark(error, &nmreq,
+ (reqlen - NFSX_UNSIGNED) | 0x80000000);
+
+ *mreqp = mreq;
+ return (0);
+}
+
+/*
+ * Parse an NFS file attribute structure out of an mbuf chain.
+ */
+int
+nfs_parsefattr(struct nfsm_chain *nmc, int nfsvers, struct nfs_vattr *nvap)
+{
+ int error = 0;
+ enum vtype vtype;
+ u_short vmode;
+ uint32_t val, val2;
+ dev_t rdev;
+
+ val = val2 = 0;
+ NVATTR_INIT(nvap);
+
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TYPE);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_MODE);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_NUMLINKS);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_OWNER);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_OWNER_GROUP);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_SIZE);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_SPACE_USED);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_RAWDEV);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_FSID);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_FILEID);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TIME_ACCESS);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TIME_MODIFY);
+ NFS_BITMAP_SET(nvap->nva_bitmap, NFS_FATTR_TIME_METADATA);
+
+ nfsm_chain_get_32(error, nmc, vtype);
+ nfsm_chain_get_32(error, nmc, vmode);
+ nfsmout_if(error);
+
+ if (nfsvers == NFS_VER3) {
+ nvap->nva_type = nfstov_type(vtype, nfsvers);
+ } else {
+ /*
+ * The duplicate information returned in fa_type and fa_mode
+ * is an ambiguity in the NFS version 2 protocol.
+ *
+ * VREG should be taken literally as a regular file. If a
+ * server intends to return some type information differently
+ * in the upper bits of the mode field (e.g. for sockets, or
+ * FIFOs), NFSv2 mandates fa_type to be VNON. Anyway, we
+ * leave the examination of the mode bits even in the VREG
+ * case to avoid breakage for bogus servers, but we make sure
+ * that there are actually type bits set in the upper part of
+ * fa_mode (and failing that, trust the va_type field).
+ *
+ * NFSv3 cleared the issue, and requires fa_mode to not
+ * contain any type information (while also introducing
+ * sockets and FIFOs for fa_type).
+ */
+ vtype = nfstov_type(vtype, nfsvers);
+ if ((vtype == VNON) || ((vtype == VREG) && ((vmode & S_IFMT) != 0)))
+ vtype = IFTOVT(vmode);
+ nvap->nva_type = vtype;
+ }
+
+ nvap->nva_mode = (vmode & 07777);
+
+ nfsm_chain_get_32(error, nmc, nvap->nva_nlink);
+ nfsm_chain_get_32(error, nmc, nvap->nva_uid);
+ nfsm_chain_get_32(error, nmc, nvap->nva_gid);
+
+ if (nfsvers == NFS_VER3) {
+ nfsm_chain_get_64(error, nmc, nvap->nva_size);
+ nfsm_chain_get_64(error, nmc, nvap->nva_bytes);
+ nfsm_chain_get_32(error, nmc, nvap->nva_rawdev.specdata1);
+ nfsm_chain_get_32(error, nmc, nvap->nva_rawdev.specdata2);
+ nfsmout_if(error);
+ nfsm_chain_get_64(error, nmc, nvap->nva_fsid.major);
+ nvap->nva_fsid.minor = 0;
+ nfsm_chain_get_64(error, nmc, nvap->nva_fileid);
+ } else {
+ nfsm_chain_get_32(error, nmc, nvap->nva_size);
+ nfsm_chain_adv(error, nmc, NFSX_UNSIGNED);
+ nfsm_chain_get_32(error, nmc, rdev);
+ nfsmout_if(error);
+ nvap->nva_rawdev.specdata1 = major(rdev);
+ nvap->nva_rawdev.specdata2 = minor(rdev);
+ nfsm_chain_get_32(error, nmc, val); /* blocks */
+ nfsmout_if(error);
+ nvap->nva_bytes = val * NFS_FABLKSIZE;
+ nfsm_chain_get_32(error, nmc, val);
+ nfsmout_if(error);
+ nvap->nva_fsid.major = (uint64_t)val;
+ nvap->nva_fsid.minor = 0;
+ nfsm_chain_get_32(error, nmc, val);
+ nfsmout_if(error);
+ nvap->nva_fileid = (uint64_t)val;
+ /* Really ugly NFSv2 kludge. */
+ if ((vtype == VCHR) && (rdev == (dev_t)0xffffffff))
+ nvap->nva_type = VFIFO;
+ }
+ nfsm_chain_get_time(error, nmc, nfsvers,
+ nvap->nva_timesec[NFSTIME_ACCESS],
+ nvap->nva_timensec[NFSTIME_ACCESS]);
+ nfsm_chain_get_time(error, nmc, nfsvers,
+ nvap->nva_timesec[NFSTIME_MODIFY],
+ nvap->nva_timensec[NFSTIME_MODIFY]);
+ nfsm_chain_get_time(error, nmc, nfsvers,
+ nvap->nva_timesec[NFSTIME_CHANGE],
+ nvap->nva_timensec[NFSTIME_CHANGE]);
+nfsmout:
+ return (error);
+}
+
+/*
+ * Load the attribute cache (that lives in the nfsnode entry) with
+ * the value pointed to by nvap, unless the file type in the attribute
+ * cache doesn't match the file type in the nvap, in which case log a
+ * warning and return ESTALE.
+ *
+ * If the dontshrink flag is set, then it's not safe to call ubc_setsize()
+ * to shrink the size of the file.
+ */
+int
+nfs_loadattrcache(
+ nfsnode_t np,
+ struct nfs_vattr *nvap,
+ u_int64_t *xidp,
+ int dontshrink)
+{
+ mount_t mp;
+ vnode_t vp;
+ struct timeval now;
+ struct nfs_vattr *npnvap;
+ int xattr = np->n_vattr.nva_flags & NFS_FFLAG_IS_ATTR;
+ int referral = np->n_vattr.nva_flags & NFS_FFLAG_TRIGGER_REFERRAL;
+ int aclbit, monitored, error = 0;
+ kauth_acl_t acl;
+ struct nfsmount *nmp;
+ uint32_t events = np->n_events;
+
+ if (np->n_hflag & NHINIT) {
+ vp = NULL;
+ mp = np->n_mount;
+ } else {
+ vp = NFSTOV(np);
+ mp = vnode_mount(vp);
+ }
+ monitored = vp ? vnode_ismonitored(vp) : 0;
+
+ FSDBG_TOP(527, np, vp, *xidp >> 32, *xidp);
+
+ if (!((nmp = VFSTONFS(mp)))) {
+ FSDBG_BOT(527, ENXIO, 1, 0, *xidp);
+ return (ENXIO);
+ }
+
+ if (*xidp < np->n_xid) {
+ /*
+ * We have already updated attributes with a response from
+ * a later request. The attributes we have here are probably
+ * stale so we drop them (just return). However, our
+ * out-of-order receipt could be correct - if the requests were
+ * processed out of order at the server. Given the uncertainty
+ * we invalidate our cached attributes. *xidp is zeroed here
+ * to indicate the attributes were dropped - only getattr
+ * cares - it needs to retry the rpc.
+ */
+ NATTRINVALIDATE(np);
+ FSDBG_BOT(527, 0, np, np->n_xid, *xidp);
+ *xidp = 0;
+ return (0);
+ }
+
+ if (vp && (nvap->nva_type != vnode_vtype(vp))) {
+ /*
+ * The filehandle has changed type on us. This can be
+ * caused by either the server not having unique filehandles
+ * or because another client has removed the previous
+ * filehandle and a new object (of a different type)
+ * has been created with the same filehandle.
+ *
+ * We can't simply switch the type on the vnode because
+ * there may be type-specific fields that need to be
+ * cleaned up or set up.
+ *
+ * So, what should we do with this vnode?
+ *
+ * About the best we can do is log a warning and return
+ * an error. ESTALE is about the closest error, but it
+ * is a little strange that we come up with this error
+ * internally instead of simply passing it through from
+ * the server. Hopefully, the vnode will be reclaimed
+ * soon so the filehandle can be reincarnated as the new
+ * object type.
+ */
+ printf("nfs loadattrcache vnode changed type, was %d now %d\n",
+ vnode_vtype(vp), nvap->nva_type);
+ error = ESTALE;
+ if (monitored)
+ events |= VNODE_EVENT_DELETE;
+ goto out;
+ }