summaryrefslogtreecommitdiffstats
path: root/sys/fs/nfsserver
diff options
context:
space:
mode:
Diffstat (limited to 'sys/fs/nfsserver')
-rw-r--r--sys/fs/nfsserver/nfs_nfsdcache.c867
-rw-r--r--sys/fs/nfsserver/nfs_nfsdkrpc.c455
-rw-r--r--sys/fs/nfsserver/nfs_nfsdport.c3060
-rw-r--r--sys/fs/nfsserver/nfs_nfsdserv.c3367
-rw-r--r--sys/fs/nfsserver/nfs_nfsdsocket.c979
-rw-r--r--sys/fs/nfsserver/nfs_nfsdstate.c4891
-rw-r--r--sys/fs/nfsserver/nfs_nfsdsubs.c2021
7 files changed, 15640 insertions, 0 deletions
diff --git a/sys/fs/nfsserver/nfs_nfsdcache.c b/sys/fs/nfsserver/nfs_nfsdcache.c
new file mode 100644
index 0000000..a83510e
--- /dev/null
+++ b/sys/fs/nfsserver/nfs_nfsdcache.c
@@ -0,0 +1,867 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Here is the basic algorithm:
+ * First, some design criteria I used:
+ * - I think a false hit is more serious than a false miss
+ * - A false hit for an RPC that has Op(s) that order via seqid# must be
+ * avoided at all cost
+ * - A valid hit will probably happen a long time after the original reply
+ * and the TCP socket that the original request was received on will no
+ * longer be active
+ * (The long time delay implies to me that LRU is not appropriate.)
+ * - The mechanism will satisfy the requirements of ordering Ops with seqid#s
+ * in them as well as minimizing the risk of redoing retried non-idempotent
+ * Ops.
+ * Because it is biased towards avoiding false hits, multiple entries with
+ * the same xid are to be expected, especially for the case of the entry
+ * in the cache being related to a seqid# sequenced Op.
+ *
+ * The basic algorithm I'm about to code up:
+ * - Null RPCs bypass the cache and are just done
+ * For TCP
+ * - key on <xid, NFS version> (as noted above, there can be several
+ * entries with the same key)
+ * When a request arrives:
+ * For all that match key
+ * - if RPC# != OR request_size !=
+ * - not a match with this one
+ * - if NFSv4 and received on same TCP socket OR
+ * received on a TCP connection created before the
+ * entry was cached
+ * - not a match with this one
+ * (V2,3 clients might retry on same TCP socket)
+ * - calculate checksum on first N bytes of NFS XDR
+ * - if checksum !=
+ * - not a match for this one
+ * If any of the remaining ones that match has a
+ * seqid_refcnt > 0
+ * - not a match (go do RPC, using new cache entry)
+ * If one match left
+ * - a hit (reply from cache)
+ * else
+ * - miss (go do RPC, using new cache entry)
+ *
+ * During processing of NFSv4 request:
+ * - set a flag when a non-idempotent Op is processed
+ * - when an Op that uses a seqid# (Open,...) is processed
+ * - if same seqid# as referenced entry in cache
+ * - free new cache entry
+ * - reply from referenced cache entry
+ * else if next seqid# in order
+ * - free referenced cache entry
+ * - increment seqid_refcnt on new cache entry
+ * - set pointer from Openowner/Lockowner to
+ * new cache entry (aka reference it)
+ * else if first seqid# in sequence
+ * - increment seqid_refcnt on new cache entry
+ * - set pointer from Openowner/Lockowner to
+ * new cache entry (aka reference it)
+ *
+ * At end of RPC processing:
+ * - if seqid_refcnt > 0 OR flagged non-idempotent on new
+ * cache entry
+ * - save reply in cache entry
+ * - calculate checksum on first N bytes of NFS XDR
+ * request
+ * - note op and length of XDR request (in bytes)
+ * - timestamp it
+ * else
+ * - free new cache entry
+ * - Send reply (noting info for socket activity check, below)
+ *
+ * For cache entries saved above:
+ * - if saved since seqid_refcnt was > 0
+ * - free when seqid_refcnt decrements to 0
+ * (when next one in sequence is processed above, or
+ * when Openowner/Lockowner is discarded)
+ * else { non-idempotent Op(s) }
+ * - free when
+ * - some further activity observed on same
+ * socket
+ * (I'm not yet sure how I'm going to do
+ * this. Maybe look at the TCP connection
+ * to see if the send_tcp_sequence# is well
+ * past sent reply OR K additional RPCs
+ * replied on same socket OR?)
+ * OR
+ * - when very old (hours, days, weeks?)
+ *
+ * For UDP (v2, 3 only), pretty much the old way:
+ * - key on <xid, NFS version, RPC#, Client host ip#>
+ * (at most one entry for each key)
+ *
+ * When a Request arrives:
+ * - if a match with entry via key
+ * - if RPC marked In_progress
+ * - discard request (don't send reply)
+ * else
+ * - reply from cache
+ * - timestamp cache entry
+ * else
+ * - add entry to cache, marked In_progress
+ * - do RPC
+ * - when RPC done
+ * - if RPC# non-idempotent
+ * - mark entry Done (not In_progress)
+ * - save reply
+ * - timestamp cache entry
+ * else
+ * - free cache entry
+ * - send reply
+ *
+ * Later, entries with saved replies are free'd a short time (few minutes)
+ * after reply sent (timestamp).
+ * Reference: Chet Juszczak, "Improving the Performance and Correctness
+ * of an NFS Server", in Proc. Winter 1989 USENIX Conference,
+ * pages 53-63. San Diego, February 1989.
+ * for the UDP case.
+ * nfsrc_floodlevel is set to the allowable upper limit for saved replies
+ * for TCP. For V3, a reply won't be saved when the flood level is
+ * hit. For V4, the non-idempotent Op will return NFSERR_RESOURCE in
+ * that case. This level should be set high enough that this almost
+ * never happens.
+ */
+#ifndef APPLEKEXT
+#include <fs/nfs/nfsport.h>
+
+extern struct nfsstats newnfsstats;
+NFSCACHEMUTEX;
+int nfsrc_floodlevel = NFSRVCACHE_FLOODLEVEL, nfsrc_tcpsavedreplies = 0;
+#endif /* !APPLEKEXT */
+
+static int nfsrc_tcpnonidempotent = 1;
+static int nfsrc_udphighwater = NFSRVCACHE_UDPHIGHWATER, nfsrc_udpcachesize = 0;
+static TAILQ_HEAD(, nfsrvcache) nfsrvudplru;
+static struct nfsrvhashhead nfsrvhashtbl[NFSRVCACHE_HASHSIZE],
+ nfsrvudphashtbl[NFSRVCACHE_HASHSIZE];
+/*
+ * and the reverse mapping from generic to Version 2 procedure numbers
+ */
+static int newnfsv2_procid[NFS_V3NPROCS] = {
+ NFSV2PROC_NULL,
+ NFSV2PROC_GETATTR,
+ NFSV2PROC_SETATTR,
+ NFSV2PROC_LOOKUP,
+ NFSV2PROC_NOOP,
+ NFSV2PROC_READLINK,
+ NFSV2PROC_READ,
+ NFSV2PROC_WRITE,
+ NFSV2PROC_CREATE,
+ NFSV2PROC_MKDIR,
+ NFSV2PROC_SYMLINK,
+ NFSV2PROC_CREATE,
+ NFSV2PROC_REMOVE,
+ NFSV2PROC_RMDIR,
+ NFSV2PROC_RENAME,
+ NFSV2PROC_LINK,
+ NFSV2PROC_READDIR,
+ NFSV2PROC_NOOP,
+ NFSV2PROC_STATFS,
+ NFSV2PROC_NOOP,
+ NFSV2PROC_NOOP,
+ NFSV2PROC_NOOP,
+};
+
+#define NFSRCUDPHASH(xid) \
+ (&nfsrvudphashtbl[((xid) + ((xid) >> 24)) % NFSRVCACHE_HASHSIZE])
+#define NFSRCHASH(xid) \
+ (&nfsrvhashtbl[((xid) + ((xid) >> 24)) % NFSRVCACHE_HASHSIZE])
+#define TRUE 1
+#define FALSE 0
+#define NFSRVCACHE_CHECKLEN 100
+
+/* True iff the rpc reply is an nfs status ONLY! */
+static int nfsv2_repstat[NFS_V3NPROCS] = {
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ TRUE,
+ TRUE,
+ TRUE,
+ TRUE,
+ FALSE,
+ TRUE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+};
+
+/*
+ * Will NFS want to work over IPv6 someday?
+ */
+#define NETFAMILY(rp) \
+ (((rp)->rc_flag & RC_INETIPV6) ? AF_INET6 : AF_INET)
+
+/* local functions */
+static int nfsrc_getudp(struct nfsrv_descript *nd, struct nfsrvcache *newrp);
+static int nfsrc_gettcp(struct nfsrv_descript *nd, struct nfsrvcache *newrp);
+static void nfsrc_lock(struct nfsrvcache *rp);
+static void nfsrc_unlock(struct nfsrvcache *rp);
+static void nfsrc_wanted(struct nfsrvcache *rp);
+static void nfsrc_freecache(struct nfsrvcache *rp);
+static void nfsrc_trimcache(u_int64_t, struct socket *);
+static int nfsrc_activesocket(struct nfsrvcache *rp, u_int64_t,
+ struct socket *);
+static int nfsrc_getlenandcksum(mbuf_t m1, u_int16_t *cksum);
+static void nfsrc_marksametcpconn(u_int64_t);
+
+/*
+ * Initialize the server request cache list
+ */
+APPLESTATIC void
+nfsrvd_initcache(void)
+{
+ int i;
+ static int inited = 0;
+
+ if (inited)
+ return;
+ inited = 1;
+ for (i = 0; i < NFSRVCACHE_HASHSIZE; i++) {
+ LIST_INIT(&nfsrvudphashtbl[i]);
+ LIST_INIT(&nfsrvhashtbl[i]);
+ }
+ TAILQ_INIT(&nfsrvudplru);
+ nfsrc_tcpsavedreplies = 0;
+ nfsrc_udpcachesize = 0;
+ newnfsstats.srvcache_tcppeak = 0;
+ newnfsstats.srvcache_size = 0;
+}
+
+/*
+ * Get a cache entry for this request. Basically just malloc a new one
+ * and then call nfsrc_getudp() or nfsrc_gettcp() to do the rest.
+ * Call nfsrc_trimcache() to clean up the cache before returning.
+ */
+APPLESTATIC int
+nfsrvd_getcache(struct nfsrv_descript *nd, struct socket *so)
+{
+ struct nfsrvcache *newrp;
+ int ret;
+
+ if (nd->nd_procnum == NFSPROC_NULL)
+ panic("nfsd cache null");
+ MALLOC(newrp, struct nfsrvcache *, sizeof (struct nfsrvcache),
+ M_NFSRVCACHE, M_WAITOK);
+ NFSBZERO((caddr_t)newrp, sizeof (struct nfsrvcache));
+ if (nd->nd_flag & ND_NFSV4)
+ newrp->rc_flag = RC_NFSV4;
+ else if (nd->nd_flag & ND_NFSV3)
+ newrp->rc_flag = RC_NFSV3;
+ else
+ newrp->rc_flag = RC_NFSV2;
+ newrp->rc_xid = nd->nd_retxid;
+ newrp->rc_proc = nd->nd_procnum;
+ newrp->rc_sockref = nd->nd_sockref;
+ newrp->rc_cachetime = nd->nd_tcpconntime;
+ if (nd->nd_flag & ND_SAMETCPCONN)
+ newrp->rc_flag |= RC_SAMETCPCONN;
+ if (nd->nd_nam2 != NULL) {
+ newrp->rc_flag |= RC_UDP;
+ ret = nfsrc_getudp(nd, newrp);
+ } else {
+ ret = nfsrc_gettcp(nd, newrp);
+ }
+ nfsrc_trimcache(nd->nd_sockref, so);
+ return (ret);
+}
+
+/*
+ * For UDP (v2, v3):
+ * - key on <xid, NFS version, RPC#, Client host ip#>
+ * (at most one entry for each key)
+ */
+static int
+nfsrc_getudp(struct nfsrv_descript *nd, struct nfsrvcache *newrp)
+{
+ struct nfsrvcache *rp;
+ struct sockaddr_in *saddr;
+ struct sockaddr_in6 *saddr6;
+ struct nfsrvhashhead *hp;
+ int ret = 0;
+
+ hp = NFSRCUDPHASH(newrp->rc_xid);
+loop:
+ NFSLOCKCACHE();
+ LIST_FOREACH(rp, hp, rc_hash) {
+ if (newrp->rc_xid == rp->rc_xid &&
+ newrp->rc_proc == rp->rc_proc &&
+ (newrp->rc_flag & rp->rc_flag & RC_NFSVERS) &&
+ nfsaddr_match(NETFAMILY(rp), &rp->rc_haddr, nd->nd_nam)) {
+ if ((rp->rc_flag & RC_LOCKED) != 0) {
+ rp->rc_flag |= RC_WANTED;
+ NFSUNLOCKCACHE();
+ (void) tsleep((caddr_t)rp, PZERO - 1,
+ "nfsrc", 10 * hz);
+ goto loop;
+ }
+ if (rp->rc_flag == 0)
+ panic("nfs udp cache0");
+ rp->rc_flag |= RC_LOCKED;
+ TAILQ_REMOVE(&nfsrvudplru, rp, rc_lru);
+ TAILQ_INSERT_TAIL(&nfsrvudplru, rp, rc_lru);
+ if (rp->rc_flag & RC_INPROG) {
+ newnfsstats.srvcache_inproghits++;
+ NFSUNLOCKCACHE();
+ ret = RC_DROPIT;
+ } else if (rp->rc_flag & RC_REPSTATUS) {
+ /*
+ * V2 only.
+ */
+ newnfsstats.srvcache_nonidemdonehits++;
+ NFSUNLOCKCACHE();
+ nfsrvd_rephead(nd);
+ *(nd->nd_errp) = rp->rc_status;
+ ret = RC_REPLY;
+ rp->rc_timestamp = NFSD_MONOSEC +
+ NFSRVCACHE_UDPTIMEOUT;
+ } else if (rp->rc_flag & RC_REPMBUF) {
+ newnfsstats.srvcache_nonidemdonehits++;
+ NFSUNLOCKCACHE();
+ nd->nd_mreq = m_copym(rp->rc_reply, 0,
+ M_COPYALL, M_WAIT);
+ ret = RC_REPLY;
+ rp->rc_timestamp = NFSD_MONOSEC +
+ NFSRVCACHE_UDPTIMEOUT;
+ } else {
+ panic("nfs udp cache1");
+ }
+ nfsrc_unlock(rp);
+ free((caddr_t)newrp, M_NFSRVCACHE);
+ return (ret);
+ }
+ }
+ newnfsstats.srvcache_misses++;
+ newnfsstats.srvcache_size++;
+ nfsrc_udpcachesize++;
+
+ newrp->rc_flag |= RC_INPROG;
+ saddr = NFSSOCKADDR(nd->nd_nam, struct sockaddr_in *);
+ if (saddr->sin_family == AF_INET)
+ newrp->rc_inet = saddr->sin_addr.s_addr;
+ else if (saddr->sin_family == AF_INET6) {
+ saddr6 = (struct sockaddr_in6 *)saddr;
+ NFSBCOPY((caddr_t)&saddr6->sin6_addr,(caddr_t)&newrp->rc_inet6,
+ sizeof (struct in6_addr));
+ rp->rc_flag |= RC_INETIPV6;
+ }
+ LIST_INSERT_HEAD(hp, newrp, rc_hash);
+ TAILQ_INSERT_TAIL(&nfsrvudplru, newrp, rc_lru);
+ NFSUNLOCKCACHE();
+ nd->nd_rp = newrp;
+ return (RC_DOIT);
+}
+
+/*
+ * Update a request cache entry after the rpc has been done
+ */
+APPLESTATIC struct nfsrvcache *
+nfsrvd_updatecache(struct nfsrv_descript *nd, struct socket *so)
+{
+ struct nfsrvcache *rp;
+ struct nfsrvcache *retrp = NULL;
+
+ rp = nd->nd_rp;
+ if (!rp)
+ panic("nfsrvd_updatecache null rp");
+ nd->nd_rp = NULL;
+ NFSLOCKCACHE();
+ nfsrc_lock(rp);
+ if (!(rp->rc_flag & RC_INPROG))
+ panic("nfsrvd_updatecache not inprog");
+ rp->rc_flag &= ~RC_INPROG;
+ if (rp->rc_flag & RC_UDP) {
+ TAILQ_REMOVE(&nfsrvudplru, rp, rc_lru);
+ TAILQ_INSERT_TAIL(&nfsrvudplru, rp, rc_lru);
+ }
+
+ /*
+ * Reply from cache is a special case returned by nfsrv_checkseqid().
+ */
+ if (nd->nd_repstat == NFSERR_REPLYFROMCACHE) {
+ newnfsstats.srvcache_nonidemdonehits++;
+ NFSUNLOCKCACHE();
+ nd->nd_repstat = 0;
+ if (nd->nd_mreq)
+ mbuf_freem(nd->nd_mreq);
+ if (!(rp->rc_flag & RC_REPMBUF))
+ panic("reply from cache");
+ nd->nd_mreq = m_copym(rp->rc_reply, 0,
+ M_COPYALL, M_WAIT);
+ rp->rc_timestamp = NFSD_MONOSEC + NFSRVCACHE_TCPTIMEOUT;
+ nfsrc_unlock(rp);
+ nfsrc_trimcache(nd->nd_sockref, so);
+ return (retrp);
+ }
+
+ /*
+ * If rc_refcnt > 0, save it
+ * For UDP, save it if ND_SAVEREPLY is set
+ * For TCP, save it if ND_SAVEREPLY and nfsrc_tcpnonidempotent is set
+ */
+ if (nd->nd_repstat != NFSERR_DONTREPLY &&
+ (rp->rc_refcnt > 0 ||
+ ((nd->nd_flag & ND_SAVEREPLY) && (rp->rc_flag & RC_UDP)) ||
+ ((nd->nd_flag & ND_SAVEREPLY) && !(rp->rc_flag & RC_UDP) &&
+ nfsrc_tcpsavedreplies <= nfsrc_floodlevel &&
+ nfsrc_tcpnonidempotent))) {
+ if (rp->rc_refcnt > 0) {
+ if (!(rp->rc_flag & RC_NFSV4))
+ panic("update_cache refcnt");
+ rp->rc_flag |= RC_REFCNT;
+ }
+ if ((nd->nd_flag & ND_NFSV2) &&
+ nfsv2_repstat[newnfsv2_procid[nd->nd_procnum]]) {
+ NFSUNLOCKCACHE();
+ rp->rc_status = nd->nd_repstat;
+ rp->rc_flag |= RC_REPSTATUS;
+ } else {
+ if (!(rp->rc_flag & RC_UDP)) {
+ nfsrc_tcpsavedreplies++;
+ if (nfsrc_tcpsavedreplies >
+ newnfsstats.srvcache_tcppeak)
+ newnfsstats.srvcache_tcppeak =
+ nfsrc_tcpsavedreplies;
+ }
+ NFSUNLOCKCACHE();
+ rp->rc_reply = m_copym(nd->nd_mreq, 0, M_COPYALL,
+ M_WAIT);
+ rp->rc_flag |= RC_REPMBUF;
+ }
+ if (rp->rc_flag & RC_UDP) {
+ rp->rc_timestamp = NFSD_MONOSEC +
+ NFSRVCACHE_UDPTIMEOUT;
+ nfsrc_unlock(rp);
+ } else {
+ rp->rc_timestamp = NFSD_MONOSEC +
+ NFSRVCACHE_TCPTIMEOUT;
+ if (rp->rc_refcnt > 0)
+ nfsrc_unlock(rp);
+ else
+ retrp = rp;
+ }
+ } else {
+ nfsrc_freecache(rp);
+ NFSUNLOCKCACHE();
+ }
+ nfsrc_trimcache(nd->nd_sockref, so);
+ return (retrp);
+}
+
+/*
+ * Invalidate and, if possible, free an in prog cache entry.
+ * Must not sleep.
+ */
+APPLESTATIC void
+nfsrvd_delcache(struct nfsrvcache *rp)
+{
+
+ if (!(rp->rc_flag & RC_INPROG))
+ panic("nfsrvd_delcache not in prog");
+ NFSLOCKCACHE();
+ rp->rc_flag &= ~RC_INPROG;
+ if (rp->rc_refcnt == 0 && !(rp->rc_flag & RC_LOCKED))
+ nfsrc_freecache(rp);
+ NFSUNLOCKCACHE();
+}
+
+/*
+ * Called after nfsrvd_updatecache() once the reply is sent, to update
+ * the entry for nfsrc_activesocket() and unlock it. The argument is
+ * the pointer returned by nfsrvd_updatecache().
+ */
+APPLESTATIC void
+nfsrvd_sentcache(struct nfsrvcache *rp, struct socket *so, int err)
+{
+
+ if (!(rp->rc_flag & RC_LOCKED))
+ panic("nfsrvd_sentcache not locked");
+ if (!err) {
+ if (so->so_proto->pr_domain->dom_family != AF_INET ||
+ so->so_proto->pr_protocol != IPPROTO_TCP)
+ panic("nfs sent cache");
+ if (nfsrv_getsockseqnum(so, &rp->rc_tcpseq))
+ rp->rc_flag |= RC_TCPSEQ;
+ }
+ nfsrc_unlock(rp);
+}
+
+/*
+ * Get a cache entry for TCP
+ * - key on <xid, nfs version>
+ * (allow multiple entries for a given key)
+ */
+static int
+nfsrc_gettcp(struct nfsrv_descript *nd, struct nfsrvcache *newrp)
+{
+ struct nfsrvcache *rp, *nextrp;
+ int i;
+ struct nfsrvcache *hitrp;
+ struct nfsrvhashhead *hp, nfsrc_templist;
+ int hit, ret = 0;
+
+ hp = NFSRCHASH(newrp->rc_xid);
+ newrp->rc_reqlen = nfsrc_getlenandcksum(nd->nd_mrep, &newrp->rc_cksum);
+tryagain:
+ NFSLOCKCACHE();
+ hit = 1;
+ LIST_INIT(&nfsrc_templist);
+ /*
+ * Get all the matches and put them on the temp list.
+ */
+ rp = LIST_FIRST(hp);
+ while (rp != LIST_END(hp)) {
+ nextrp = LIST_NEXT(rp, rc_hash);
+ if (newrp->rc_xid == rp->rc_xid &&
+ (!(rp->rc_flag & RC_INPROG) ||
+ ((newrp->rc_flag & RC_SAMETCPCONN) &&
+ newrp->rc_sockref == rp->rc_sockref)) &&
+ (newrp->rc_flag & rp->rc_flag & RC_NFSVERS) &&
+ newrp->rc_proc == rp->rc_proc &&
+ ((newrp->rc_flag & RC_NFSV4) &&
+ newrp->rc_sockref != rp->rc_sockref &&
+ newrp->rc_cachetime >= rp->rc_cachetime)
+ && newrp->rc_reqlen == rp->rc_reqlen &&
+ newrp->rc_cksum == rp->rc_cksum) {
+ LIST_REMOVE(rp, rc_hash);
+ LIST_INSERT_HEAD(&nfsrc_templist, rp, rc_hash);
+ }
+ rp = nextrp;
+ }
+
+ /*
+ * Now, use nfsrc_templist to decide if there is a match.
+ */
+ i = 0;
+ LIST_FOREACH(rp, &nfsrc_templist, rc_hash) {
+ i++;
+ if (rp->rc_refcnt > 0) {
+ hit = 0;
+ break;
+ }
+ }
+ /*
+ * Can be a hit only if one entry left.
+ * Note possible hit entry and put nfsrc_templist back on hash
+ * list.
+ */
+ if (i != 1)
+ hit = 0;
+ hitrp = rp = LIST_FIRST(&nfsrc_templist);
+ while (rp != LIST_END(&nfsrc_templist)) {
+ nextrp = LIST_NEXT(rp, rc_hash);
+ LIST_REMOVE(rp, rc_hash);
+ LIST_INSERT_HEAD(hp, rp, rc_hash);
+ rp = nextrp;
+ }
+ if (LIST_FIRST(&nfsrc_templist) != LIST_END(&nfsrc_templist))
+ panic("nfs gettcp cache templist");
+
+ if (hit) {
+ rp = hitrp;
+ if ((rp->rc_flag & RC_LOCKED) != 0) {
+ rp->rc_flag |= RC_WANTED;
+ NFSUNLOCKCACHE();
+ (void) tsleep((caddr_t)rp, PZERO-1, "nfsrc", 10 * hz);
+ goto tryagain;
+ }
+ if (rp->rc_flag == 0)
+ panic("nfs tcp cache0");
+ rp->rc_flag |= RC_LOCKED;
+ if (rp->rc_flag & RC_INPROG) {
+ newnfsstats.srvcache_inproghits++;
+ NFSUNLOCKCACHE();
+ if (newrp->rc_sockref == rp->rc_sockref)
+ nfsrc_marksametcpconn(rp->rc_sockref);
+ ret = RC_DROPIT;
+ } else if (rp->rc_flag & RC_REPSTATUS) {
+ /*
+ * V2 only.
+ */
+ newnfsstats.srvcache_nonidemdonehits++;
+ NFSUNLOCKCACHE();
+ if (newrp->rc_sockref == rp->rc_sockref)
+ nfsrc_marksametcpconn(rp->rc_sockref);
+ ret = RC_REPLY;
+ nfsrvd_rephead(nd);
+ *(nd->nd_errp) = rp->rc_status;
+ rp->rc_timestamp = NFSD_MONOSEC +
+ NFSRVCACHE_TCPTIMEOUT;
+ } else if (rp->rc_flag & RC_REPMBUF) {
+ newnfsstats.srvcache_nonidemdonehits++;
+ NFSUNLOCKCACHE();
+ if (newrp->rc_sockref == rp->rc_sockref)
+ nfsrc_marksametcpconn(rp->rc_sockref);
+ ret = RC_REPLY;
+ nd->nd_mreq = m_copym(rp->rc_reply, 0,
+ M_COPYALL, M_WAIT);
+ rp->rc_timestamp = NFSD_MONOSEC +
+ NFSRVCACHE_TCPTIMEOUT;
+ } else {
+ panic("nfs tcp cache1");
+ }
+ nfsrc_unlock(rp);
+ free((caddr_t)newrp, M_NFSRVCACHE);
+ return (ret);
+ }
+ newnfsstats.srvcache_misses++;
+ newnfsstats.srvcache_size++;
+
+ /*
+ * For TCP, multiple entries for a key are allowed, so don't
+ * chain it into the hash table until done.
+ */
+ newrp->rc_cachetime = NFSD_MONOSEC;
+ newrp->rc_flag |= RC_INPROG;
+ LIST_INSERT_HEAD(hp, newrp, rc_hash);
+ NFSUNLOCKCACHE();
+ nd->nd_rp = newrp;
+ return (RC_DOIT);
+}
+
+/*
+ * Lock a cache entry.
+ * Also puts a mutex lock on the cache list.
+ */
+static void
+nfsrc_lock(struct nfsrvcache *rp)
+{
+ NFSCACHELOCKREQUIRED();
+ while ((rp->rc_flag & RC_LOCKED) != 0) {
+ rp->rc_flag |= RC_WANTED;
+ (void) nfsmsleep((caddr_t)rp, NFSCACHEMUTEXPTR, PZERO - 1,
+ "nfsrc", 0);
+ }
+ rp->rc_flag |= RC_LOCKED;
+}
+
+/*
+ * Unlock a cache entry.
+ */
+static void
+nfsrc_unlock(struct nfsrvcache *rp)
+{
+ rp->rc_flag &= ~RC_LOCKED;
+ nfsrc_wanted(rp);
+}
+
+/*
+ * Wakeup anyone wanting entry.
+ */
+static void
+nfsrc_wanted(struct nfsrvcache *rp)
+{
+ if (rp->rc_flag & RC_WANTED) {
+ rp->rc_flag &= ~RC_WANTED;
+ wakeup((caddr_t)rp);
+ }
+}
+
+/*
+ * Free up the entry.
+ * Must not sleep.
+ */
+static void
+nfsrc_freecache(struct nfsrvcache *rp)
+{
+
+ NFSCACHELOCKREQUIRED();
+ LIST_REMOVE(rp, rc_hash);
+ if (rp->rc_flag & RC_UDP) {
+ TAILQ_REMOVE(&nfsrvudplru, rp, rc_lru);
+ nfsrc_udpcachesize--;
+ }
+ nfsrc_wanted(rp);
+ if (rp->rc_flag & RC_REPMBUF) {
+ mbuf_freem(rp->rc_reply);
+ if (!(rp->rc_flag & RC_UDP))
+ nfsrc_tcpsavedreplies--;
+ }
+ FREE((caddr_t)rp, M_NFSRVCACHE);
+ newnfsstats.srvcache_size--;
+}
+
+#ifdef notdef
+/*
+ * Clean out the cache. Called when the last nfsd terminates.
+ */
+APPLESTATIC void
+nfsrvd_cleancache(void)
+{
+ struct nfsrvcache *rp, *nextrp;
+ int i;
+
+ NFSLOCKCACHE();
+ for (i = 0; i < NFSRVCACHE_HASHSIZE; i++) {
+ LIST_FOREACH_SAFE(rp, &nfsrvhashtbl[i], rc_hash, nextrp) {
+ nfsrc_freecache(rp);
+ }
+ }
+ for (i = 0; i < NFSRVCACHE_HASHSIZE; i++) {
+ LIST_FOREACH_SAFE(rp, &nfsrvudphashtbl[i], rc_hash, nextrp) {
+ nfsrc_freecache(rp);
+ }
+ }
+ newnfsstats.srvcache_size = 0;
+ nfsrc_tcpsavedreplies = 0;
+ NFSUNLOCKCACHE();
+}
+#endif /* notdef */
+
+/*
+ * The basic rule is to get rid of entries that are expired.
+ */
+static void
+nfsrc_trimcache(u_int64_t sockref, struct socket *so)
+{
+ struct nfsrvcache *rp, *nextrp;
+ int i;
+
+ NFSLOCKCACHE();
+ TAILQ_FOREACH_SAFE(rp, &nfsrvudplru, rc_lru, nextrp) {
+ if (!(rp->rc_flag & (RC_INPROG|RC_LOCKED|RC_WANTED))
+ && rp->rc_refcnt == 0
+ && ((rp->rc_flag & RC_REFCNT) ||
+ NFSD_MONOSEC > rp->rc_timestamp ||
+ nfsrc_udpcachesize > nfsrc_udphighwater))
+ nfsrc_freecache(rp);
+ }
+ for (i = 0; i < NFSRVCACHE_HASHSIZE; i++) {
+ LIST_FOREACH_SAFE(rp, &nfsrvhashtbl[i], rc_hash, nextrp) {
+ if (!(rp->rc_flag & (RC_INPROG|RC_LOCKED|RC_WANTED))
+ && rp->rc_refcnt == 0
+ && ((rp->rc_flag & RC_REFCNT) ||
+ NFSD_MONOSEC > rp->rc_timestamp ||
+ nfsrc_activesocket(rp, sockref, so)))
+ nfsrc_freecache(rp);
+ }
+ }
+ NFSUNLOCKCACHE();
+}
+
+/*
+ * Add a seqid# reference to the cache entry.
+ */
+APPLESTATIC void
+nfsrvd_refcache(struct nfsrvcache *rp)
+{
+
+ NFSLOCKCACHE();
+ if (rp->rc_refcnt < 0)
+ panic("nfs cache refcnt");
+ rp->rc_refcnt++;
+ NFSUNLOCKCACHE();
+}
+
+/*
+ * Dereference a seqid# cache entry.
+ */
+APPLESTATIC void
+nfsrvd_derefcache(struct nfsrvcache *rp)
+{
+
+ NFSLOCKCACHE();
+ if (rp->rc_refcnt <= 0)
+ panic("nfs cache derefcnt");
+ rp->rc_refcnt--;
+ if (rp->rc_refcnt == 0 && !(rp->rc_flag & (RC_LOCKED | RC_INPROG)))
+ nfsrc_freecache(rp);
+ NFSUNLOCKCACHE();
+}
+
+/*
+ * Check to see if the socket is active.
+ * Return 1 if the reply has been received/acknowledged by the client,
+ * 0 otherwise.
+ * XXX - Uses tcp internals.
+ */
+static int
+nfsrc_activesocket(struct nfsrvcache *rp, u_int64_t cur_sockref,
+ struct socket *cur_so)
+{
+ int ret = 0;
+
+ if (!(rp->rc_flag & RC_TCPSEQ))
+ return (ret);
+ /*
+ * If the sockref is the same, it is the same TCP connection.
+ */
+ if (cur_sockref == rp->rc_sockref)
+ ret = nfsrv_checksockseqnum(cur_so, rp->rc_tcpseq);
+ return (ret);
+}
+
+/*
+ * Calculate the length of the mbuf list and a checksum on the first up to
+ * NFSRVCACHE_CHECKLEN bytes.
+ */
+static int
+nfsrc_getlenandcksum(mbuf_t m1, u_int16_t *cksum)
+{
+ int len = 0, cklen;
+ mbuf_t m;
+
+ m = m1;
+ while (m) {
+ len += mbuf_len(m);
+ m = mbuf_next(m);
+ }
+ cklen = (len > NFSRVCACHE_CHECKLEN) ? NFSRVCACHE_CHECKLEN : len;
+ *cksum = in_cksum(m1, cklen);
+ return (len);
+}
+
+/*
+ * Mark a TCP connection that is seeing retries. Should never happen for
+ * NFSv4.
+ */
+static void
+nfsrc_marksametcpconn(u_int64_t sockref)
+{
+}
+
diff --git a/sys/fs/nfsserver/nfs_nfsdkrpc.c b/sys/fs/nfsserver/nfs_nfsdkrpc.c
new file mode 100644
index 0000000..0b8df95
--- /dev/null
+++ b/sys/fs/nfsserver/nfs_nfsdkrpc.c
@@ -0,0 +1,455 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_inet6.h"
+#include "opt_kgssapi.h"
+
+#include <fs/nfs/nfsport.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpcsec_gss.h>
+
+NFSDLOCKMUTEX;
+
+/*
+ * Mapping of old NFS Version 2 RPC numbers to generic numbers.
+ */
+static int newnfs_nfsv3_procid[NFS_V3NPROCS] = {
+ NFSPROC_NULL,
+ NFSPROC_GETATTR,
+ NFSPROC_SETATTR,
+ NFSPROC_NOOP,
+ NFSPROC_LOOKUP,
+ NFSPROC_READLINK,
+ NFSPROC_READ,
+ NFSPROC_NOOP,
+ NFSPROC_WRITE,
+ NFSPROC_CREATE,
+ NFSPROC_REMOVE,
+ NFSPROC_RENAME,
+ NFSPROC_LINK,
+ NFSPROC_SYMLINK,
+ NFSPROC_MKDIR,
+ NFSPROC_RMDIR,
+ NFSPROC_READDIR,
+ NFSPROC_FSSTAT,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP,
+};
+
+
+SYSCTL_DECL(_vfs_newnfs);
+
+SVCPOOL *nfsrvd_pool;
+
+static int nfs_privport = 0;
+SYSCTL_INT(_vfs_newnfs, OID_AUTO, nfs_privport, CTLFLAG_RW,
+ &nfs_privport, 0,
+ "Only allow clients using a privileged port for NFSv2 and 3");
+
+static int nfs_proc(struct nfsrv_descript *, u_int32_t, struct socket *,
+ u_int64_t, struct nfsrvcache **);
+
+extern u_long sb_max_adj;
+extern int newnfs_numnfsd;
+
+/*
+ * NFS server system calls
+ */
+
+static void
+nfssvc_program(struct svc_req *rqst, SVCXPRT *xprt)
+{
+ struct nfsrv_descript nd;
+ struct nfsrvcache *rp = NULL;
+ int cacherep;
+
+ memset(&nd, 0, sizeof(nd));
+ if (rqst->rq_vers == NFS_VER2) {
+ if (rqst->rq_proc > NFSV2PROC_STATFS) {
+ svcerr_noproc(rqst);
+ svc_freereq(rqst);
+ return;
+ }
+ nd.nd_procnum = newnfs_nfsv3_procid[rqst->rq_proc];
+ nd.nd_flag = ND_NFSV2;
+ } else if (rqst->rq_vers == NFS_VER3) {
+ if (rqst->rq_proc >= NFS_V3NPROCS) {
+ svcerr_noproc(rqst);
+ svc_freereq(rqst);
+ return;
+ }
+ nd.nd_procnum = rqst->rq_proc;
+ nd.nd_flag = ND_NFSV3;
+ } else {
+ if (rqst->rq_proc != NFSPROC_NULL &&
+ rqst->rq_proc != NFSV4PROC_COMPOUND) {
+ svcerr_noproc(rqst);
+ svc_freereq(rqst);
+ return;
+ }
+ nd.nd_procnum = rqst->rq_proc;
+ nd.nd_flag = ND_NFSV4;
+ }
+
+ /*
+ * Note: we want rq_addr, not svc_getrpccaller for nd_nam2 -
+ * NFS_SRVMAXDATA uses a NULL value for nd_nam2 to detect TCP
+ * mounts.
+ */
+ nd.nd_mrep = rqst->rq_args;
+ rqst->rq_args = NULL;
+ newnfs_realign(&nd.nd_mrep);
+ nd.nd_md = nd.nd_mrep;
+ nd.nd_dpos = mtod(nd.nd_md, caddr_t);
+ nd.nd_nam = svc_getrpccaller(rqst);
+ nd.nd_nam2 = rqst->rq_addr;
+ nd.nd_mreq = NULL;
+ nd.nd_cred = NULL;
+
+ if (nfs_privport && (nd.nd_flag & ND_NFSV4) == 0) {
+ /* Check if source port is privileged */
+ u_short port;
+ struct sockaddr *nam = nd.nd_nam;
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)nam;
+ /*
+ * INET/INET6 - same code:
+ * sin_port and sin6_port are at same offset
+ */
+ port = ntohs(sin->sin_port);
+ if (port >= IPPORT_RESERVED &&
+ nd.nd_procnum != NFSPROC_NULL) {
+#ifdef INET6
+ char b6[INET6_ADDRSTRLEN];
+#if defined(KLD_MODULE)
+ /* Do not use ip6_sprintf: the nfs module should work without INET6. */
+#define ip6_sprintf(buf, a) \
+ (sprintf((buf), "%x:%x:%x:%x:%x:%x:%x:%x", \
+ (a)->s6_addr16[0], (a)->s6_addr16[1], \
+ (a)->s6_addr16[2], (a)->s6_addr16[3], \
+ (a)->s6_addr16[4], (a)->s6_addr16[5], \
+ (a)->s6_addr16[6], (a)->s6_addr16[7]), \
+ (buf))
+#endif
+#endif
+ printf("NFS request from unprivileged port (%s:%d)\n",
+#ifdef INET6
+ sin->sin_family == AF_INET6 ?
+ ip6_sprintf(b6, &satosin6(sin)->sin6_addr) :
+#if defined(KLD_MODULE)
+#undef ip6_sprintf
+#endif
+#endif
+ inet_ntoa(sin->sin_addr), port);
+ svcerr_weakauth(rqst);
+ svc_freereq(rqst);
+ m_freem(nd.nd_mrep);
+ return;
+ }
+ }
+
+ if (nd.nd_procnum != NFSPROC_NULL) {
+ if (!svc_getcred(rqst, &nd.nd_cred, &nd.nd_credflavor)) {
+ svcerr_weakauth(rqst);
+ svc_freereq(rqst);
+ m_freem(nd.nd_mrep);
+ return;
+ }
+#ifdef MAC
+ mac_cred_associate_nfsd(nd.nd_cred);
+#endif
+ if ((nd.nd_flag & ND_NFSV4))
+ nd.nd_repstat = nfsvno_v4rootexport(&nd);
+
+ cacherep = nfs_proc(&nd, rqst->rq_xid, xprt->xp_socket,
+ xprt->xp_sockref, &rp);
+ } else {
+ NFSMGET(nd.nd_mreq);
+ nd.nd_mreq->m_len = 0;
+ cacherep = RC_REPLY;
+ }
+ if (nd.nd_mrep != NULL)
+ m_freem(nd.nd_mrep);
+
+ if (nd.nd_cred != NULL)
+ crfree(nd.nd_cred);
+
+ if (cacherep == RC_DROPIT) {
+ if (nd.nd_mreq != NULL)
+ m_freem(nd.nd_mreq);
+ svc_freereq(rqst);
+ return;
+ }
+
+ if (nd.nd_mreq == NULL) {
+ svcerr_decode(rqst);
+ svc_freereq(rqst);
+ return;
+ }
+
+ if (nd.nd_repstat & NFSERR_AUTHERR) {
+ svcerr_auth(rqst, nd.nd_repstat & ~NFSERR_AUTHERR);
+ if (nd.nd_mreq != NULL)
+ m_freem(nd.nd_mreq);
+ } else if (!svc_sendreply_mbuf(rqst, nd.nd_mreq)) {
+ svcerr_systemerr(rqst);
+ }
+ if (rp != NULL)
+ nfsrvd_sentcache(rp, xprt->xp_socket, 0);
+ svc_freereq(rqst);
+}
+
+/*
+ * Check the cache and, optionally, do the RPC.
+ * Return the appropriate cache response.
+ */
+static int
+nfs_proc(struct nfsrv_descript *nd, u_int32_t xid, struct socket *so,
+ u_int64_t sockref, struct nfsrvcache **rpp)
+{
+ struct thread *td = curthread;
+ int cacherep = RC_DOIT, isdgram;
+
+ *rpp = NULL;
+ if (nd->nd_nam2 == NULL) {
+ nd->nd_flag |= ND_STREAMSOCK;
+ isdgram = 0;
+ } else {
+ isdgram = 1;
+ }
+ NFSGETTIME(&nd->nd_starttime);
+
+ /*
+ * Several cases:
+ * 1 - For NFSv2 over UDP, if we are near our malloc/mget
+ * limit, just drop the request. There is no
+ * NFSERR_RESOURCE or NFSERR_DELAY for NFSv2 and the
+ * client will timeout/retry over UDP in a little while.
+ * 2 - nd_repstat set to some error, so generate the reply now.
+ * 3 - nd_repstat == 0 && nd_mreq == NULL, which
+ * means a normal nfs rpc, so check the cache
+ */
+ if ((nd->nd_flag & ND_NFSV2) && nd->nd_nam2 != NULL &&
+ nfsrv_mallocmget_limit()) {
+ cacherep = RC_DROPIT;
+ } else if (nd->nd_repstat) {
+ cacherep = RC_REPLY;
+ } else {
+ /*
+ * For NFSv3, play it safe and assume that the client is
+ * doing retries on the same TCP connection.
+ */
+ if ((nd->nd_flag & (ND_NFSV4 | ND_STREAMSOCK)) ==
+ ND_STREAMSOCK)
+ nd->nd_flag |= ND_SAMETCPCONN;
+ nd->nd_retxid = xid;
+ nd->nd_tcpconntime = NFSD_MONOSEC;
+ nd->nd_sockref = sockref;
+ cacherep = nfsrvd_getcache(nd, so);
+ }
+
+ /*
+ * Handle the request. There are three cases.
+ * RC_DOIT - do the RPC
+ * RC_REPLY - return the reply already created
+ * RC_DROPIT - just throw the request away
+ */
+ if (cacherep == RC_DOIT) {
+ nfsrvd_dorpc(nd, isdgram, td);
+ if (nd->nd_repstat == NFSERR_DONTREPLY)
+ cacherep = RC_DROPIT;
+ else
+ cacherep = RC_REPLY;
+ *rpp = nfsrvd_updatecache(nd, so);
+ }
+ return (cacherep);
+}
+
+/*
+ * Adds a socket to the list for servicing by nfsds.
+ */
+int
+nfsrvd_addsock(struct file *fp)
+{
+ int siz;
+ struct socket *so;
+ int error;
+ SVCXPRT *xprt;
+ static u_int64_t sockref = 0;
+
+ so = fp->f_data;
+
+ siz = sb_max_adj;
+ error = soreserve(so, siz, siz);
+ if (error) {
+ return (error);
+ }
+
+ /*
+ * Steal the socket from userland so that it doesn't close
+ * unexpectedly.
+ */
+ if (so->so_type == SOCK_DGRAM)
+ xprt = svc_dg_create(nfsrvd_pool, so, 0, 0);
+ else
+ xprt = svc_vc_create(nfsrvd_pool, so, 0, 0);
+ if (xprt) {
+ fp->f_ops = &badfileops;
+ fp->f_data = NULL;
+ xprt->xp_sockref = ++sockref;
+ svc_reg(xprt, NFS_PROG, NFS_VER2, nfssvc_program, NULL);
+ svc_reg(xprt, NFS_PROG, NFS_VER3, nfssvc_program, NULL);
+ svc_reg(xprt, NFS_PROG, NFS_VER4, nfssvc_program, NULL);
+ }
+
+ return (0);
+}
+
+/*
+ * Called by nfssvc() for nfsds. Just loops around servicing rpc requests
+ * until it is killed by a signal.
+ */
+int
+nfsrvd_nfsd(struct thread *td, struct nfsd_nfsd_args *args)
+{
+#ifdef KGSSAPI
+ char principal[128];
+ int error;
+ bool_t ret2, ret3, ret4;
+#endif
+
+#ifdef KGSSAPI
+ if (args != NULL) {
+ error = copyinstr(args->principal, principal,
+ sizeof(principal), NULL);
+ if (error)
+ return (error);
+ } else {
+ snprintf(principal, sizeof(principal), "nfs@%s", hostname);
+ }
+#endif
+
+ /*
+ * Only the first nfsd actually does any work. The RPC code
+ * adds threads to it as needed. Any extra processes offered
+ * by nfsd just exit. If nfsd is new enough, it will call us
+ * once with a structure that specifies how many threads to
+ * use.
+ */
+ NFSD_LOCK();
+ if (newnfs_numnfsd == 0) {
+ newnfs_numnfsd++;
+
+ NFSD_UNLOCK();
+
+#ifdef KGSSAPI
+ ret2 = rpc_gss_set_svc_name(principal, "kerberosv5",
+ GSS_C_INDEFINITE, NFS_PROG, NFS_VER2);
+ ret3 = rpc_gss_set_svc_name(principal, "kerberosv5",
+ GSS_C_INDEFINITE, NFS_PROG, NFS_VER3);
+ ret4 = rpc_gss_set_svc_name(principal, "kerberosv5",
+ GSS_C_INDEFINITE, NFS_PROG, NFS_VER4);
+
+ /*
+ * If the principal name was specified, these should have
+ * succeeded.
+ */
+ if (args != NULL && principal[0] != '\0' &&
+ (!ret2 || !ret3 || !ret4)) {
+ NFSD_LOCK();
+ newnfs_numnfsd--;
+ NFSD_UNLOCK();
+ return (EAUTH);
+ }
+#endif
+
+ if (args != NULL) {
+ nfsrvd_pool->sp_minthreads = args->minthreads;
+ nfsrvd_pool->sp_maxthreads = args->maxthreads;
+ } else {
+ nfsrvd_pool->sp_minthreads = 4;
+ nfsrvd_pool->sp_maxthreads = 4;
+ }
+
+ svc_run(nfsrvd_pool);
+
+#ifdef KGSSAPI
+ rpc_gss_clear_svc_name(NFS_PROG, NFS_VER2);
+ rpc_gss_clear_svc_name(NFS_PROG, NFS_VER3);
+ rpc_gss_clear_svc_name(NFS_PROG, NFS_VER4);
+#endif
+
+ NFSD_LOCK();
+ newnfs_numnfsd--;
+ nfsrvd_init(1);
+ }
+ NFSD_UNLOCK();
+
+ return (0);
+}
+
+/*
+ * Initialize the data structures for the server.
+ * Handshake with any new nfsds starting up to avoid any chance of
+ * corruption.
+ */
+void
+nfsrvd_init(int terminating)
+{
+
+ NFSD_LOCK_ASSERT();
+
+ if (terminating) {
+ NFSD_UNLOCK();
+ svcpool_destroy(nfsrvd_pool);
+ nfsrvd_pool = NULL;
+ NFSD_LOCK();
+ }
+
+ NFSD_UNLOCK();
+
+ nfsrvd_pool = svcpool_create("nfsd", SYSCTL_STATIC_CHILDREN(_vfs_newnfs));
+ nfsrvd_pool->sp_rcache = NULL;
+ nfsrvd_pool->sp_assign = NULL;
+ nfsrvd_pool->sp_done = NULL;
+
+ NFSD_LOCK();
+}
+
diff --git a/sys/fs/nfsserver/nfs_nfsdport.c b/sys/fs/nfsserver/nfs_nfsdport.c
new file mode 100644
index 0000000..6d39173
--- /dev/null
+++ b/sys/fs/nfsserver/nfs_nfsdport.c
@@ -0,0 +1,3060 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Functions that perform the vfs operations required by the routines in
+ * nfsd_serv.c. It is hoped that this change will make the server more
+ * portable.
+ */
+
+#include <fs/nfs/nfsport.h>
+#include <sys/sysctl.h>
+
+extern int nfsrv_dolocallocks;
+extern u_int32_t newnfs_true, newnfs_false, newnfs_xdrneg1;
+extern int nfsv4root_set;
+extern int nfsrv_useacl;
+extern int newnfs_numnfsd;
+extern struct mount nfsv4root_mnt;
+extern struct nfsrv_stablefirst nfsrv_stablefirst;
+extern void (*nfsd_call_servertimer)(void);
+struct vfsoptlist nfsv4root_opt, nfsv4root_newopt;
+NFSDLOCKMUTEX;
+struct mtx nfs_cache_mutex;
+struct mtx nfs_v4root_mutex;
+struct nfsrvfh nfs_rootfh, nfs_pubfh;
+int nfs_pubfhset = 0, nfs_rootfhset = 0;
+
+static int nfssvc_srvcall(struct thread *, struct nfssvc_args *, struct ucred *);
+
+static int enable_crossmntpt = 1;
+static int nfs_commit_blks;
+static int nfs_commit_miss;
+extern int nfsrv_issuedelegs;
+SYSCTL_DECL(_vfs_newnfs);
+SYSCTL_INT(_vfs_newnfs, OID_AUTO, mirrormnt, CTLFLAG_RW, &enable_crossmntpt, 0, "");
+SYSCTL_INT(_vfs_newnfs, OID_AUTO, commit_blks, CTLFLAG_RW, &nfs_commit_blks, 0, "");
+SYSCTL_INT(_vfs_newnfs, OID_AUTO, commit_miss, CTLFLAG_RW, &nfs_commit_miss, 0, "");
+SYSCTL_INT(_vfs_newnfs, OID_AUTO, issue_delegations, CTLFLAG_RW, &nfsrv_issuedelegs, 0, "");
+
+#define NUM_HEURISTIC 1017
+#define NHUSE_INIT 64
+#define NHUSE_INC 16
+#define NHUSE_MAX 2048
+
+static struct nfsheur {
+ struct vnode *nh_vp; /* vp to match (unreferenced pointer) */
+ off_t nh_nextr; /* next offset for sequential detection */
+ int nh_use; /* use count for selection */
+ int nh_seqcount; /* heuristic */
+} nfsheur[NUM_HEURISTIC];
+
+
+/*
+ * Get attributes into nfsvattr structure.
+ */
+int
+nfsvno_getattr(struct vnode *vp, struct nfsvattr *nvap, struct ucred *cred,
+ struct thread *p)
+{
+ int error, lockedit = 0;
+
+ /* Since FreeBSD insists the vnode be locked... */
+ if (VOP_ISLOCKED(vp) != LK_EXCLUSIVE) {
+ lockedit = 1;
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ }
+ error = VOP_GETATTR(vp, &nvap->na_vattr, cred);
+ if (lockedit)
+ NFSVOPUNLOCK(vp, 0, p);
+ return (error);
+}
+
+/*
+ * Get a file handle for a vnode.
+ */
+int
+nfsvno_getfh(struct vnode *vp, fhandle_t *fhp, struct thread *p)
+{
+ int error;
+
+ NFSBZERO((caddr_t)fhp, sizeof(fhandle_t));
+ fhp->fh_fsid = vp->v_mount->mnt_stat.f_fsid;
+ error = VOP_VPTOFH(vp, &fhp->fh_fid);
+ return (error);
+}
+
+/*
+ * Perform access checking for vnodes obtained from file handles that would
+ * refer to files already opened by a Unix client. You cannot just use
+ * vn_writechk() and VOP_ACCESS() for two reasons.
+ * 1 - You must check for exported rdonly as well as MNT_RDONLY for the write case
+ * 2 - The owner is to be given access irrespective of mode bits for some
+ * operations, so that processes that chmod after opening a file don't
+ * break.
+ */
+int
+nfsvno_accchk(struct vnode *vp, u_int32_t accessbits, struct ucred *cred,
+ struct nfsexstuff *exp, struct thread *p, int override, int vpislocked)
+{
+ struct vattr vattr;
+ int error = 0, getret = 0;
+ accmode_t accmode;
+
+ /*
+ * Convert accessbits to Vxxx flags.
+ */
+ if (accessbits & (NFSV4ACE_WRITEDATA | NFSV4ACE_APPENDDATA |
+ NFSV4ACE_ADDFILE | NFSV4ACE_ADDSUBDIRECTORY |
+ NFSV4ACE_DELETECHILD | NFSV4ACE_WRITEATTRIBUTES |
+ NFSV4ACE_DELETE | NFSV4ACE_WRITEACL | NFSV4ACE_WRITEOWNER))
+ accmode = VWRITE;
+ else if (accessbits & (NFSV4ACE_EXECUTE | NFSV4ACE_SEARCH))
+ accmode = VEXEC;
+ else
+ accmode = VREAD;
+
+ if (accmode & VWRITE) {
+ /* Just vn_writechk() changed to check rdonly */
+ /*
+ * Disallow write attempts on read-only file systems;
+ * unless the file is a socket or a block or character
+ * device resident on the file system.
+ */
+ if (NFSVNO_EXRDONLY(exp) ||
+ (vp->v_mount->mnt_flag & MNT_RDONLY)) {
+ switch (vp->v_type) {
+ case VREG:
+ case VDIR:
+ case VLNK:
+ return (EROFS);
+ default:
+ break;
+ }
+ }
+ /*
+ * If there's shared text associated with
+ * the inode, try to free it up once. If
+ * we fail, we can't allow writing.
+ */
+ if (vp->v_vflag & VV_TEXT)
+ return (ETXTBSY);
+ }
+ if (vpislocked == 0)
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+
+#ifdef NFS4_ACL_EXTATTR_NAME
+ /*
+ * Should the override still be applied when ACLs are enabled?
+ */
+ if (nfsrv_useacl != 0 && NFSHASNFS4ACL(vp->v_mount))
+ error = nfsrv_aclaccess(vp, accmode, accessbits, cred, p);
+ else
+#endif
+ if (accessbits == NFSV4ACE_READATTRIBUTES)
+ error = 0;
+ else
+ error = VOP_ACCESS(vp, accmode, cred, p);
+
+ /*
+ * Allow certain operations for the owner (reads and writes
+ * on files that are already open).
+ */
+ if (override != NFSACCCHK_NOOVERRIDE &&
+ (error == EPERM || error == EACCES)) {
+ if (cred->cr_uid == 0 && (override & NFSACCCHK_ALLOWROOT))
+ error = 0;
+ else if (override & NFSACCCHK_ALLOWOWNER) {
+ getret = VOP_GETATTR(vp, &vattr, cred);
+ if (getret == 0 && cred->cr_uid == vattr.va_uid)
+ error = 0;
+ }
+ }
+ if (vpislocked == 0)
+ NFSVOPUNLOCK(vp, 0, p);
+ return (error);
+}
+
+/*
+ * Set attribute(s) vnop.
+ */
+int
+nfsvno_setattr(struct vnode *vp, struct nfsvattr *nvap, struct ucred *cred,
+ struct thread *p, struct nfsexstuff *exp)
+{
+ int error;
+
+ error = VOP_SETATTR(vp, &nvap->na_vattr, cred);
+ return (error);
+}
+
+/*
+ * Set up nameidata for a lookup() call and do it
+ * For the cases where we are crossing mount points
+ * (looking up the public fh path or the v4 root path when
+ * not using a pseudo-root fs), set/release the Giant lock,
+ * as required.
+ */
+int
+nfsvno_namei(struct nfsrv_descript *nd, struct nameidata *ndp,
+ struct vnode *dp, int islocked, struct nfsexstuff *exp, struct thread *p,
+ struct vnode **retdirp)
+{
+ struct componentname *cnp = &ndp->ni_cnd;
+ int i;
+ struct iovec aiov;
+ struct uio auio;
+ int lockleaf = (cnp->cn_flags & LOCKLEAF) != 0, linklen;
+ int error = 0, crossmnt;
+ char *cp;
+
+ *retdirp = NULL;
+ cnp->cn_nameptr = cnp->cn_pnbuf;
+ /*
+ * Extract and set starting directory.
+ */
+ if (dp->v_type != VDIR) {
+ if (islocked)
+ vput(dp);
+ else
+ vrele(dp);
+ nfsvno_relpathbuf(ndp);
+ return (ENOTDIR);
+ }
+ if (islocked)
+ NFSVOPUNLOCK(dp, 0, p);
+ VREF(dp);
+ *retdirp = dp;
+ if (NFSVNO_EXRDONLY(exp))
+ cnp->cn_flags |= RDONLY;
+ ndp->ni_segflg = UIO_SYSSPACE;
+ crossmnt = 1;
+
+ if (nd->nd_flag & ND_PUBLOOKUP) {
+ ndp->ni_loopcnt = 0;
+ if (cnp->cn_pnbuf[0] == '/') {
+ vrele(dp);
+ /*
+ * Check for degenerate pathnames here, since lookup()
+ * panics on them.
+ */
+ for (i = 1; i < ndp->ni_pathlen; i++)
+ if (cnp->cn_pnbuf[i] != '/')
+ break;
+ if (i == ndp->ni_pathlen) {
+ error = NFSERR_ACCES;
+ goto out;
+ }
+ dp = rootvnode;
+ VREF(dp);
+ }
+ } else if ((enable_crossmntpt == 0 && NFSVNO_EXPORTED(exp)) ||
+ (nd->nd_flag & ND_NFSV4) == 0) {
+ /*
+ * Only cross mount points for NFSv4 when doing a
+ * mount while traversing the file system above
+ * the mount point, unless enable_crossmntpt is set.
+ */
+ cnp->cn_flags |= NOCROSSMOUNT;
+ crossmnt = 0;
+ }
+
+ /*
+ * Initialize for scan, set ni_startdir and bump ref on dp again
+ * becuase lookup() will dereference ni_startdir.
+ */
+
+ cnp->cn_thread = p;
+ ndp->ni_startdir = dp;
+ ndp->ni_rootdir = rootvnode;
+
+ if (!lockleaf)
+ cnp->cn_flags |= LOCKLEAF;
+ for (;;) {
+ cnp->cn_nameptr = cnp->cn_pnbuf;
+ /*
+ * Call lookup() to do the real work. If an error occurs,
+ * ndp->ni_vp and ni_dvp are left uninitialized or NULL and
+ * we do not have to dereference anything before returning.
+ * In either case ni_startdir will be dereferenced and NULLed
+ * out.
+ */
+ if (exp->nes_vfslocked)
+ ndp->ni_cnd.cn_flags |= GIANTHELD;
+ error = lookup(ndp);
+ /*
+ * The Giant lock should only change when
+ * crossing mount points.
+ */
+ if (crossmnt) {
+ exp->nes_vfslocked =
+ (ndp->ni_cnd.cn_flags & GIANTHELD) != 0;
+ ndp->ni_cnd.cn_flags &= ~GIANTHELD;
+ }
+ if (error)
+ break;
+
+ /*
+ * Check for encountering a symbolic link. Trivial
+ * termination occurs if no symlink encountered.
+ */
+ if ((cnp->cn_flags & ISSYMLINK) == 0) {
+ if ((cnp->cn_flags & (SAVENAME | SAVESTART)) == 0)
+ nfsvno_relpathbuf(ndp);
+ if (ndp->ni_vp && !lockleaf)
+ NFSVOPUNLOCK(ndp->ni_vp, 0, p);
+ break;
+ }
+
+ /*
+ * Validate symlink
+ */
+ if ((cnp->cn_flags & LOCKPARENT) && ndp->ni_pathlen == 1)
+ NFSVOPUNLOCK(ndp->ni_dvp, 0, p);
+ if (!(nd->nd_flag & ND_PUBLOOKUP)) {
+ error = EINVAL;
+ goto badlink2;
+ }
+
+ if (ndp->ni_loopcnt++ >= MAXSYMLINKS) {
+ error = ELOOP;
+ goto badlink2;
+ }
+ if (ndp->ni_pathlen > 1)
+ cp = uma_zalloc(namei_zone, M_WAITOK);
+ else
+ cp = cnp->cn_pnbuf;
+ aiov.iov_base = cp;
+ aiov.iov_len = MAXPATHLEN;
+ auio.uio_iov = &aiov;
+ auio.uio_iovcnt = 1;
+ auio.uio_offset = 0;
+ auio.uio_rw = UIO_READ;
+ auio.uio_segflg = UIO_SYSSPACE;
+ auio.uio_td = NULL;
+ auio.uio_resid = MAXPATHLEN;
+ error = VOP_READLINK(ndp->ni_vp, &auio, cnp->cn_cred);
+ if (error) {
+ badlink1:
+ if (ndp->ni_pathlen > 1)
+ uma_zfree(namei_zone, cp);
+ badlink2:
+ vrele(ndp->ni_dvp);
+ vput(ndp->ni_vp);
+ break;
+ }
+ linklen = MAXPATHLEN - auio.uio_resid;
+ if (linklen == 0) {
+ error = ENOENT;
+ goto badlink1;
+ }
+ if (linklen + ndp->ni_pathlen >= MAXPATHLEN) {
+ error = ENAMETOOLONG;
+ goto badlink1;
+ }
+
+ /*
+ * Adjust or replace path
+ */
+ if (ndp->ni_pathlen > 1) {
+ NFSBCOPY(ndp->ni_next, cp + linklen, ndp->ni_pathlen);
+ uma_zfree(namei_zone, cnp->cn_pnbuf);
+ cnp->cn_pnbuf = cp;
+ } else
+ cnp->cn_pnbuf[linklen] = '\0';
+ ndp->ni_pathlen += linklen;
+
+ /*
+ * Cleanup refs for next loop and check if root directory
+ * should replace current directory. Normally ni_dvp
+ * becomes the new base directory and is cleaned up when
+ * we loop. Explicitly null pointers after invalidation
+ * to clarify operation.
+ */
+ vput(ndp->ni_vp);
+ ndp->ni_vp = NULL;
+
+ if (cnp->cn_pnbuf[0] == '/') {
+ vrele(ndp->ni_dvp);
+ ndp->ni_dvp = ndp->ni_rootdir;
+ VREF(ndp->ni_dvp);
+ }
+ ndp->ni_startdir = ndp->ni_dvp;
+ ndp->ni_dvp = NULL;
+ }
+ if (!lockleaf)
+ cnp->cn_flags &= ~LOCKLEAF;
+
+out:
+ if (error) {
+ uma_zfree(namei_zone, cnp->cn_pnbuf);
+ ndp->ni_vp = NULL;
+ ndp->ni_dvp = NULL;
+ ndp->ni_startdir = NULL;
+ cnp->cn_flags &= ~HASBUF;
+ } else if ((ndp->ni_cnd.cn_flags & (WANTPARENT|LOCKPARENT)) == 0) {
+ ndp->ni_dvp = NULL;
+ }
+ return (error);
+}
+
+/*
+ * Set up a pathname buffer and return a pointer to it and, optionally
+ * set a hash pointer.
+ */
+void
+nfsvno_setpathbuf(struct nameidata *ndp, char **bufpp, u_long **hashpp)
+{
+ struct componentname *cnp = &ndp->ni_cnd;
+
+ cnp->cn_flags |= (NOMACCHECK | HASBUF);
+ cnp->cn_pnbuf = uma_zalloc(namei_zone, M_WAITOK);
+ if (hashpp != NULL)
+ *hashpp = NULL;
+ *bufpp = cnp->cn_pnbuf;
+}
+
+/*
+ * Release the above path buffer, if not released by nfsvno_namei().
+ */
+void
+nfsvno_relpathbuf(struct nameidata *ndp)
+{
+
+ if ((ndp->ni_cnd.cn_flags & HASBUF) == 0)
+ panic("nfsrelpath");
+ uma_zfree(namei_zone, ndp->ni_cnd.cn_pnbuf);
+ ndp->ni_cnd.cn_flags &= ~HASBUF;
+}
+
+/*
+ * Readlink vnode op into an mbuf list.
+ */
+int
+nfsvno_readlink(struct vnode *vp, struct ucred *cred, struct thread *p,
+ struct mbuf **mpp, struct mbuf **mpendp, int *lenp)
+{
+ struct iovec iv[(NFS_MAXPATHLEN+MLEN-1)/MLEN];
+ struct iovec *ivp = iv;
+ struct uio io, *uiop = &io;
+ struct mbuf *mp, *mp2 = NULL, *mp3 = NULL;
+ int i, len, tlen, error;
+
+ len = 0;
+ i = 0;
+ while (len < NFS_MAXPATHLEN) {
+ NFSMGET(mp);
+ MCLGET(mp, M_WAIT);
+ mp->m_len = NFSMSIZ(mp);
+ if (len == 0) {
+ mp3 = mp2 = mp;
+ } else {
+ mp2->m_next = mp;
+ mp2 = mp;
+ }
+ if ((len + mp->m_len) > NFS_MAXPATHLEN) {
+ mp->m_len = NFS_MAXPATHLEN - len;
+ len = NFS_MAXPATHLEN;
+ } else {
+ len += mp->m_len;
+ }
+ ivp->iov_base = mtod(mp, caddr_t);
+ ivp->iov_len = mp->m_len;
+ i++;
+ ivp++;
+ }
+ uiop->uio_iov = iv;
+ uiop->uio_iovcnt = i;
+ uiop->uio_offset = 0;
+ uiop->uio_resid = len;
+ uiop->uio_rw = UIO_READ;
+ uiop->uio_segflg = UIO_SYSSPACE;
+ uiop->uio_td = NULL;
+ error = VOP_READLINK(vp, uiop, cred);
+ if (error) {
+ m_freem(mp3);
+ *lenp = 0;
+ return (error);
+ }
+ if (uiop->uio_resid > 0) {
+ len -= uiop->uio_resid;
+ tlen = NFSM_RNDUP(len);
+ nfsrv_adj(mp3, NFS_MAXPATHLEN - tlen, tlen - len);
+ }
+ *lenp = len;
+ *mpp = mp3;
+ *mpendp = mp;
+ return (0);
+}
+
+/*
+ * Read vnode op call into mbuf list.
+ */
+int
+nfsvno_read(struct vnode *vp, off_t off, int cnt, struct ucred *cred,
+ struct thread *p, struct mbuf **mpp, struct mbuf **mpendp)
+{
+ struct mbuf *m;
+ int i;
+ struct iovec *iv;
+ struct iovec *iv2;
+ int error = 0, len, left, siz, tlen, ioflag = 0, hi, try = 32;
+ struct mbuf *m2 = NULL, *m3;
+ struct uio io, *uiop = &io;
+ struct nfsheur *nh;
+
+ /*
+ * Calculate seqcount for heuristic
+ */
+ /*
+ * Locate best candidate
+ */
+
+ hi = ((int)(vm_offset_t)vp / sizeof(struct vnode)) % NUM_HEURISTIC;
+ nh = &nfsheur[hi];
+
+ while (try--) {
+ if (nfsheur[hi].nh_vp == vp) {
+ nh = &nfsheur[hi];
+ break;
+ }
+ if (nfsheur[hi].nh_use > 0)
+ --nfsheur[hi].nh_use;
+ hi = (hi + 1) % NUM_HEURISTIC;
+ if (nfsheur[hi].nh_use < nh->nh_use)
+ nh = &nfsheur[hi];
+ }
+
+ if (nh->nh_vp != vp) {
+ nh->nh_vp = vp;
+ nh->nh_nextr = off;
+ nh->nh_use = NHUSE_INIT;
+ if (off == 0)
+ nh->nh_seqcount = 4;
+ else
+ nh->nh_seqcount = 1;
+ }
+
+ /*
+ * Calculate heuristic
+ */
+
+ if ((off == 0 && nh->nh_seqcount > 0) || off == nh->nh_nextr) {
+ if (++nh->nh_seqcount > IO_SEQMAX)
+ nh->nh_seqcount = IO_SEQMAX;
+ } else if (nh->nh_seqcount > 1) {
+ nh->nh_seqcount = 1;
+ } else {
+ nh->nh_seqcount = 0;
+ }
+ nh->nh_use += NHUSE_INC;
+ if (nh->nh_use > NHUSE_MAX)
+ nh->nh_use = NHUSE_MAX;
+ ioflag |= nh->nh_seqcount << IO_SEQSHIFT;
+
+ len = left = NFSM_RNDUP(cnt);
+ m3 = NULL;
+ /*
+ * Generate the mbuf list with the uio_iov ref. to it.
+ */
+ i = 0;
+ while (left > 0) {
+ NFSMGET(m);
+ MCLGET(m, M_WAIT);
+ m->m_len = 0;
+ siz = min(M_TRAILINGSPACE(m), left);
+ left -= siz;
+ i++;
+ if (m3)
+ m2->m_next = m;
+ else
+ m3 = m;
+ m2 = m;
+ }
+ MALLOC(iv, struct iovec *, i * sizeof (struct iovec),
+ M_TEMP, M_WAITOK);
+ uiop->uio_iov = iv2 = iv;
+ m = m3;
+ left = len;
+ i = 0;
+ while (left > 0) {
+ if (m == NULL)
+ panic("nfsvno_read iov");
+ siz = min(M_TRAILINGSPACE(m), left);
+ if (siz > 0) {
+ iv->iov_base = mtod(m, caddr_t) + m->m_len;
+ iv->iov_len = siz;
+ m->m_len += siz;
+ left -= siz;
+ iv++;
+ i++;
+ }
+ m = m->m_next;
+ }
+ uiop->uio_iovcnt = i;
+ uiop->uio_offset = off;
+ uiop->uio_resid = len;
+ uiop->uio_rw = UIO_READ;
+ uiop->uio_segflg = UIO_SYSSPACE;
+ error = VOP_READ(vp, uiop, IO_NODELOCKED | ioflag, cred);
+ FREE((caddr_t)iv2, M_TEMP);
+ if (error) {
+ m_freem(m3);
+ *mpp = NULL;
+ return (error);
+ }
+ tlen = len - uiop->uio_resid;
+ cnt = cnt < tlen ? cnt : tlen;
+ tlen = NFSM_RNDUP(cnt);
+ if (tlen == 0) {
+ m_freem(m3);
+ m3 = NULL;
+ } else if (len != tlen || tlen != cnt)
+ nfsrv_adj(m3, len - tlen, tlen - cnt);
+ *mpp = m3;
+ *mpendp = m2;
+ return (0);
+}
+
+/*
+ * Write vnode op from an mbuf list.
+ */
+int
+nfsvno_write(struct vnode *vp, off_t off, int retlen, int cnt, int stable,
+ struct mbuf *mp, char *cp, struct ucred *cred, struct thread *p)
+{
+ struct iovec *ivp;
+ int i, len;
+ struct iovec *iv;
+ int ioflags, error;
+ struct uio io, *uiop = &io;
+
+ MALLOC(ivp, struct iovec *, cnt * sizeof (struct iovec), M_TEMP,
+ M_WAITOK);
+ uiop->uio_iov = iv = ivp;
+ uiop->uio_iovcnt = cnt;
+ i = mtod(mp, caddr_t) + mp->m_len - cp;
+ len = retlen;
+ while (len > 0) {
+ if (mp == NULL)
+ panic("nfsvno_write");
+ if (i > 0) {
+ i = min(i, len);
+ ivp->iov_base = cp;
+ ivp->iov_len = i;
+ ivp++;
+ len -= i;
+ }
+ mp = mp->m_next;
+ if (mp) {
+ i = mp->m_len;
+ cp = mtod(mp, caddr_t);
+ }
+ }
+
+ if (stable == NFSWRITE_UNSTABLE)
+ ioflags = IO_NODELOCKED;
+ else
+ ioflags = (IO_SYNC | IO_NODELOCKED);
+ uiop->uio_resid = retlen;
+ uiop->uio_rw = UIO_WRITE;
+ uiop->uio_segflg = UIO_SYSSPACE;
+ NFSUIOPROC(uiop, p);
+ uiop->uio_offset = off;
+ error = VOP_WRITE(vp, uiop, ioflags, cred);
+ FREE((caddr_t)iv, M_TEMP);
+ return (error);
+}
+
+/*
+ * Common code for creating a regular file (plus special files for V2).
+ */
+int
+nfsvno_createsub(struct nfsrv_descript *nd, struct nameidata *ndp,
+ struct vnode **vpp, struct nfsvattr *nvap, int *exclusive_flagp,
+ u_char *cverf, NFSDEV_T rdev, struct thread *p, struct nfsexstuff *exp)
+{
+ u_quad_t tempsize;
+ int error;
+
+ error = nd->nd_repstat;
+ if (!error && ndp->ni_vp == NULL) {
+ if (nvap->na_type == VREG || nvap->na_type == VSOCK) {
+ vrele(ndp->ni_startdir);
+ error = VOP_CREATE(ndp->ni_dvp,
+ &ndp->ni_vp, &ndp->ni_cnd, &nvap->na_vattr);
+ vput(ndp->ni_dvp);
+ nfsvno_relpathbuf(ndp);
+ if (!error) {
+ if (*exclusive_flagp) {
+ *exclusive_flagp = 0;
+ NFSVNO_ATTRINIT(nvap);
+ NFSBCOPY(cverf,(caddr_t)&nvap->na_atime,
+ NFSX_VERF);
+ error = VOP_SETATTR(ndp->ni_vp,
+ &nvap->na_vattr, nd->nd_cred);
+ }
+ }
+ /*
+ * NFS V2 Only. nfsrvd_mknod() does this for V3.
+ * (This implies, just get out on an error.)
+ */
+ } else if (nvap->na_type == VCHR || nvap->na_type == VBLK ||
+ nvap->na_type == VFIFO) {
+ if (nvap->na_type == VCHR && rdev == 0xffffffff)
+ nvap->na_type = VFIFO;
+ if (nvap->na_type != VFIFO &&
+ (error = priv_check_cred(nd->nd_cred,
+ PRIV_VFS_MKNOD_DEV, 0))) {
+ vrele(ndp->ni_startdir);
+ nfsvno_relpathbuf(ndp);
+ vput(ndp->ni_dvp);
+ return (error);
+ }
+ nvap->na_rdev = rdev;
+ error = VOP_MKNOD(ndp->ni_dvp, &ndp->ni_vp,
+ &ndp->ni_cnd, &nvap->na_vattr);
+ vput(ndp->ni_dvp);
+ nfsvno_relpathbuf(ndp);
+ if (error) {
+ vrele(ndp->ni_startdir);
+ return (error);
+ }
+ } else {
+ vrele(ndp->ni_startdir);
+ nfsvno_relpathbuf(ndp);
+ vput(ndp->ni_dvp);
+ return (ENXIO);
+ }
+ *vpp = ndp->ni_vp;
+ } else {
+ /*
+ * Handle cases where error is already set and/or
+ * the file exists.
+ * 1 - clean up the lookup
+ * 2 - iff !error and na_size set, truncate it
+ */
+ vrele(ndp->ni_startdir);
+ nfsvno_relpathbuf(ndp);
+ *vpp = ndp->ni_vp;
+ if (ndp->ni_dvp == *vpp)
+ vrele(ndp->ni_dvp);
+ else
+ vput(ndp->ni_dvp);
+ if (!error && nvap->na_size != VNOVAL) {
+ error = nfsvno_accchk(*vpp, NFSV4ACE_ADDFILE,
+ nd->nd_cred, exp, p, NFSACCCHK_NOOVERRIDE,
+ NFSACCCHK_VPISLOCKED);
+ if (!error) {
+ tempsize = nvap->na_size;
+ NFSVNO_ATTRINIT(nvap);
+ nvap->na_size = tempsize;
+ error = VOP_SETATTR(*vpp,
+ &nvap->na_vattr, nd->nd_cred);
+ }
+ }
+ if (error)
+ vput(*vpp);
+ }
+ return (error);
+}
+
+/*
+ * Do a mknod vnode op.
+ */
+int
+nfsvno_mknod(struct nameidata *ndp, struct nfsvattr *nvap, struct ucred *cred,
+ struct thread *p)
+{
+ int error = 0;
+ enum vtype vtyp;
+
+ vtyp = nvap->na_type;
+ /*
+ * Iff doesn't exist, create it.
+ */
+ if (ndp->ni_vp) {
+ vrele(ndp->ni_startdir);
+ nfsvno_relpathbuf(ndp);
+ vput(ndp->ni_dvp);
+ vrele(ndp->ni_vp);
+ return (EEXIST);
+ }
+ if (vtyp != VCHR && vtyp != VBLK && vtyp != VSOCK && vtyp != VFIFO) {
+ vrele(ndp->ni_startdir);
+ nfsvno_relpathbuf(ndp);
+ vput(ndp->ni_dvp);
+ return (NFSERR_BADTYPE);
+ }
+ if (vtyp == VSOCK) {
+ vrele(ndp->ni_startdir);
+ error = VOP_CREATE(ndp->ni_dvp, &ndp->ni_vp,
+ &ndp->ni_cnd, &nvap->na_vattr);
+ vput(ndp->ni_dvp);
+ nfsvno_relpathbuf(ndp);
+ } else {
+ if (nvap->na_type != VFIFO &&
+ (error = priv_check_cred(cred, PRIV_VFS_MKNOD_DEV, 0))) {
+ vrele(ndp->ni_startdir);
+ nfsvno_relpathbuf(ndp);
+ vput(ndp->ni_dvp);
+ return (error);
+ }
+ error = VOP_MKNOD(ndp->ni_dvp, &ndp->ni_vp,
+ &ndp->ni_cnd, &nvap->na_vattr);
+ vput(ndp->ni_dvp);
+ nfsvno_relpathbuf(ndp);
+ if (error)
+ vrele(ndp->ni_startdir);
+ /*
+ * Since VOP_MKNOD returns the ni_vp, I can't
+ * see any reason to do the lookup.
+ */
+ }
+ return (error);
+}
+
+/*
+ * Mkdir vnode op.
+ */
+int
+nfsvno_mkdir(struct nameidata *ndp, struct nfsvattr *nvap, uid_t saved_uid,
+ struct ucred *cred, struct thread *p, struct nfsexstuff *exp)
+{
+ int error = 0;
+
+ if (ndp->ni_vp != NULL) {
+ if (ndp->ni_dvp == ndp->ni_vp)
+ vrele(ndp->ni_dvp);
+ else
+ vput(ndp->ni_dvp);
+ vrele(ndp->ni_vp);
+ return (EEXIST);
+ }
+ error = VOP_MKDIR(ndp->ni_dvp, &ndp->ni_vp, &ndp->ni_cnd,
+ &nvap->na_vattr);
+ vput(ndp->ni_dvp);
+ nfsvno_relpathbuf(ndp);
+ return (error);
+}
+
+/*
+ * symlink vnode op.
+ */
+int
+nfsvno_symlink(struct nameidata *ndp, struct nfsvattr *nvap, char *pathcp,
+ int pathlen, int not_v2, uid_t saved_uid, struct ucred *cred, struct thread *p,
+ struct nfsexstuff *exp)
+{
+ int error = 0;
+
+ if (ndp->ni_vp) {
+ vrele(ndp->ni_startdir);
+ nfsvno_relpathbuf(ndp);
+ if (ndp->ni_dvp == ndp->ni_vp)
+ vrele(ndp->ni_dvp);
+ else
+ vput(ndp->ni_dvp);
+ vrele(ndp->ni_vp);
+ return (EEXIST);
+ }
+
+ error = VOP_SYMLINK(ndp->ni_dvp, &ndp->ni_vp, &ndp->ni_cnd,
+ &nvap->na_vattr, pathcp);
+ vput(ndp->ni_dvp);
+ vrele(ndp->ni_startdir);
+ nfsvno_relpathbuf(ndp);
+ /*
+ * Although FreeBSD still had the lookup code in
+ * it for 7/current, there doesn't seem to be any
+ * point, since VOP_SYMLINK() returns the ni_vp.
+ * Just vput it for v2.
+ */
+ if (!not_v2 && !error)
+ vput(ndp->ni_vp);
+ return (error);
+}
+
+/*
+ * Parse symbolic link arguments.
+ * This function has an ugly side effect. It will MALLOC() an area for
+ * the symlink and set iov_base to point to it, only if it succeeds.
+ * So, if it returns with uiop->uio_iov->iov_base != NULL, that must
+ * be FREE'd later.
+ */
+int
+nfsvno_getsymlink(struct nfsrv_descript *nd, struct nfsvattr *nvap,
+ struct thread *p, char **pathcpp, int *lenp)
+{
+ u_int32_t *tl;
+ char *pathcp = NULL;
+ int error = 0, len;
+ struct nfsv2_sattr *sp;
+
+ *pathcpp = NULL;
+ *lenp = 0;
+ if ((nd->nd_flag & ND_NFSV3) &&
+ (error = nfsrv_sattr(nd, nvap, NULL, NULL, p)))
+ goto nfsmout;
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ len = fxdr_unsigned(int, *tl);
+ if (len > NFS_MAXPATHLEN || len <= 0) {
+ error = EBADRPC;
+ goto nfsmout;
+ }
+ MALLOC(pathcp, caddr_t, len + 1, M_TEMP, M_WAITOK);
+ error = nfsrv_mtostr(nd, pathcp, len);
+ if (error)
+ goto nfsmout;
+ if (nd->nd_flag & ND_NFSV2) {
+ NFSM_DISSECT(sp, struct nfsv2_sattr *, NFSX_V2SATTR);
+ nvap->na_mode = fxdr_unsigned(u_int16_t, sp->sa_mode);
+ }
+ *pathcpp = pathcp;
+ *lenp = len;
+ return (0);
+nfsmout:
+ if (pathcp)
+ free(pathcp, M_TEMP);
+ return (error);
+}
+
+/*
+ * Remove a non-directory object.
+ */
+int
+nfsvno_removesub(struct nameidata *ndp, int is_v4, struct ucred *cred,
+ struct thread *p, struct nfsexstuff *exp)
+{
+ struct vnode *vp;
+ int error = 0;
+
+ vp = ndp->ni_vp;
+ if (vp->v_type == VDIR)
+ error = NFSERR_ISDIR;
+ else if (is_v4)
+ error = nfsrv_checkremove(vp, 1, p);
+ if (!error)
+ error = VOP_REMOVE(ndp->ni_dvp, vp, &ndp->ni_cnd);
+ if (ndp->ni_dvp == vp)
+ vrele(ndp->ni_dvp);
+ else
+ vput(ndp->ni_dvp);
+ vput(vp);
+ return (error);
+}
+
+/*
+ * Remove a directory.
+ */
+int
+nfsvno_rmdirsub(struct nameidata *ndp, int is_v4, struct ucred *cred,
+ struct thread *p, struct nfsexstuff *exp)
+{
+ struct vnode *vp;
+ int error = 0;
+
+ vp = ndp->ni_vp;
+ if (vp->v_type != VDIR) {
+ error = ENOTDIR;
+ goto out;
+ }
+ /*
+ * No rmdir "." please.
+ */
+ if (ndp->ni_dvp == vp) {
+ error = EINVAL;
+ goto out;
+ }
+ /*
+ * The root of a mounted filesystem cannot be deleted.
+ */
+ if (vp->v_vflag & VV_ROOT)
+ error = EBUSY;
+out:
+ if (!error)
+ error = VOP_RMDIR(ndp->ni_dvp, vp, &ndp->ni_cnd);
+ if (ndp->ni_dvp == vp)
+ vrele(ndp->ni_dvp);
+ else
+ vput(ndp->ni_dvp);
+ vput(vp);
+ return (error);
+}
+
+/*
+ * Rename vnode op.
+ */
+int
+nfsvno_rename(struct nameidata *fromndp, struct nameidata *tondp,
+ u_int32_t ndstat, u_int32_t ndflag, struct ucred *cred, struct thread *p)
+{
+ struct vnode *fvp, *tvp, *tdvp;
+ int error = 0;
+
+ fvp = fromndp->ni_vp;
+ if (ndstat) {
+ vrele(fromndp->ni_dvp);
+ vrele(fvp);
+ error = ndstat;
+ goto out1;
+ }
+ tdvp = tondp->ni_dvp;
+ tvp = tondp->ni_vp;
+ if (tvp != NULL) {
+ if (fvp->v_type == VDIR && tvp->v_type != VDIR) {
+ error = (ndflag & ND_NFSV2) ? EISDIR : EEXIST;
+ goto out;
+ } else if (fvp->v_type != VDIR && tvp->v_type == VDIR) {
+ error = (ndflag & ND_NFSV2) ? ENOTDIR : EEXIST;
+ goto out;
+ }
+ if (tvp->v_type == VDIR && tvp->v_mountedhere) {
+ error = (ndflag & ND_NFSV2) ? ENOTEMPTY : EXDEV;
+ goto out;
+ }
+
+ /*
+ * A rename to '.' or '..' results in a prematurely
+ * unlocked vnode on FreeBSD5, so I'm just going to fail that
+ * here.
+ */
+ if ((tondp->ni_cnd.cn_namelen == 1 &&
+ tondp->ni_cnd.cn_nameptr[0] == '.') ||
+ (tondp->ni_cnd.cn_namelen == 2 &&
+ tondp->ni_cnd.cn_nameptr[0] == '.' &&
+ tondp->ni_cnd.cn_nameptr[1] == '.')) {
+ error = EINVAL;
+ goto out;
+ }
+ }
+ if (fvp->v_type == VDIR && fvp->v_mountedhere) {
+ error = (ndflag & ND_NFSV2) ? ENOTEMPTY : EXDEV;
+ goto out;
+ }
+ if (fvp->v_mount != tdvp->v_mount) {
+ error = (ndflag & ND_NFSV2) ? ENOTEMPTY : EXDEV;
+ goto out;
+ }
+ if (fvp == tdvp) {
+ error = (ndflag & ND_NFSV2) ? ENOTEMPTY : EINVAL;
+ goto out;
+ }
+ if (fvp == tvp) {
+ /*
+ * If source and destination are the same, there is nothing to
+ * do. Set error to -1 to indicate this.
+ */
+ error = -1;
+ goto out;
+ }
+ if (ndflag & ND_NFSV4) {
+ NFSVOPLOCK(fvp, LK_EXCLUSIVE | LK_RETRY, p);
+ error = nfsrv_checkremove(fvp, 0, p);
+ NFSVOPUNLOCK(fvp, 0, p);
+ if (tvp && !error)
+ error = nfsrv_checkremove(tvp, 1, p);
+ } else {
+ /*
+ * For NFSv2 and NFSv3, try to get rid of the delegation, so
+ * that the NFSv4 client won't be confused by the rename.
+ * Since nfsd_recalldelegation() can only be called on an
+ * unlocked vnode at this point and fvp is the file that will
+ * still exist after the rename, just do fvp.
+ */
+ nfsd_recalldelegation(fvp, p);
+ }
+out:
+ if (!error) {
+ error = VOP_RENAME(fromndp->ni_dvp, fromndp->ni_vp,
+ &fromndp->ni_cnd, tondp->ni_dvp, tondp->ni_vp,
+ &tondp->ni_cnd);
+ } else {
+ if (tdvp == tvp)
+ vrele(tdvp);
+ else
+ vput(tdvp);
+ if (tvp)
+ vput(tvp);
+ vrele(fromndp->ni_dvp);
+ vrele(fvp);
+ if (error == -1)
+ error = 0;
+ }
+ vrele(tondp->ni_startdir);
+ nfsvno_relpathbuf(tondp);
+out1:
+ vrele(fromndp->ni_startdir);
+ nfsvno_relpathbuf(fromndp);
+ return (error);
+}
+
+/*
+ * Link vnode op.
+ */
+int
+nfsvno_link(struct nameidata *ndp, struct vnode *vp, struct ucred *cred,
+ struct thread *p, struct nfsexstuff *exp)
+{
+ struct vnode *xp;
+ int error = 0;
+
+ xp = ndp->ni_vp;
+ if (xp != NULL) {
+ error = EEXIST;
+ } else {
+ xp = ndp->ni_dvp;
+ if (vp->v_mount != xp->v_mount)
+ error = EXDEV;
+ }
+ if (!error) {
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ error = VOP_LINK(ndp->ni_dvp, vp, &ndp->ni_cnd);
+ if (ndp->ni_dvp == vp)
+ vrele(ndp->ni_dvp);
+ else
+ vput(ndp->ni_dvp);
+ NFSVOPUNLOCK(vp, 0, p);
+ } else {
+ if (ndp->ni_dvp == ndp->ni_vp)
+ vrele(ndp->ni_dvp);
+ else
+ vput(ndp->ni_dvp);
+ if (ndp->ni_vp)
+ vrele(ndp->ni_vp);
+ }
+ nfsvno_relpathbuf(ndp);
+ return (error);
+}
+
+/*
+ * Do the fsync() appropriate for the commit.
+ */
+int
+nfsvno_fsync(struct vnode *vp, u_int64_t off, int cnt, struct ucred *cred,
+ struct thread *td)
+{
+ int error = 0;
+
+ if (cnt > MAX_COMMIT_COUNT) {
+ /*
+ * Give up and do the whole thing
+ */
+ if (vp->v_object &&
+ (vp->v_object->flags & OBJ_MIGHTBEDIRTY)) {
+ VM_OBJECT_LOCK(vp->v_object);
+ vm_object_page_clean(vp->v_object, 0, 0, OBJPC_SYNC);
+ VM_OBJECT_UNLOCK(vp->v_object);
+ }
+ error = VOP_FSYNC(vp, MNT_WAIT, td);
+ } else {
+ /*
+ * Locate and synchronously write any buffers that fall
+ * into the requested range. Note: we are assuming that
+ * f_iosize is a power of 2.
+ */
+ int iosize = vp->v_mount->mnt_stat.f_iosize;
+ int iomask = iosize - 1;
+ struct bufobj *bo;
+ daddr_t lblkno;
+
+ /*
+ * Align to iosize boundry, super-align to page boundry.
+ */
+ if (off & iomask) {
+ cnt += off & iomask;
+ off &= ~(u_quad_t)iomask;
+ }
+ if (off & PAGE_MASK) {
+ cnt += off & PAGE_MASK;
+ off &= ~(u_quad_t)PAGE_MASK;
+ }
+ lblkno = off / iosize;
+
+ if (vp->v_object &&
+ (vp->v_object->flags & OBJ_MIGHTBEDIRTY)) {
+ VM_OBJECT_LOCK(vp->v_object);
+ vm_object_page_clean(vp->v_object, off / PAGE_SIZE, (cnt + PAGE_MASK) / PAGE_SIZE, OBJPC_SYNC);
+ VM_OBJECT_UNLOCK(vp->v_object);
+ }
+
+ bo = &vp->v_bufobj;
+ BO_LOCK(bo);
+ while (cnt > 0) {
+ struct buf *bp;
+
+ /*
+ * If we have a buffer and it is marked B_DELWRI we
+ * have to lock and write it. Otherwise the prior
+ * write is assumed to have already been committed.
+ *
+ * gbincore() can return invalid buffers now so we
+ * have to check that bit as well (though B_DELWRI
+ * should not be set if B_INVAL is set there could be
+ * a race here since we haven't locked the buffer).
+ */
+ if ((bp = gbincore(&vp->v_bufobj, lblkno)) != NULL) {
+ if (BUF_LOCK(bp, LK_EXCLUSIVE | LK_SLEEPFAIL |
+ LK_INTERLOCK, BO_MTX(bo)) == ENOLCK) {
+ BO_LOCK(bo);
+ continue; /* retry */
+ }
+ if ((bp->b_flags & (B_DELWRI|B_INVAL)) ==
+ B_DELWRI) {
+ bremfree(bp);
+ bp->b_flags &= ~B_ASYNC;
+ bwrite(bp);
+ ++nfs_commit_miss;
+ } else
+ BUF_UNLOCK(bp);
+ BO_LOCK(bo);
+ }
+ ++nfs_commit_blks;
+ if (cnt < iosize)
+ break;
+ cnt -= iosize;
+ ++lblkno;
+ }
+ BO_UNLOCK(bo);
+ }
+ return (error);
+}
+
+/*
+ * Statfs vnode op.
+ */
+int
+nfsvno_statfs(struct vnode *vp, struct statfs *sf, struct ucred *cred,
+ struct thread *p)
+{
+ int error;
+
+ error = VFS_STATFS(vp->v_mount, sf, p);
+ return (error);
+}
+
+/*
+ * Do the vnode op stuff for Open. Similar to nfsvno_createsub(), but
+ * must handle nfsrv_opencheck() calls after any other access checks.
+ */
+void
+nfsvno_open(struct nfsrv_descript *nd, struct nameidata *ndp,
+ nfsquad_t clientid, nfsv4stateid_t *stateidp, struct nfsstate *stp,
+ int *exclusive_flagp, struct nfsvattr *nvap, u_char *cverf, int create,
+ NFSACL_T *aclp, nfsattrbit_t *attrbitp, struct ucred *cred, struct thread *p,
+ struct nfsexstuff *exp, struct vnode **vpp)
+{
+ struct vnode *vp = NULL;
+ u_quad_t tempsize;
+ struct nfsexstuff nes;
+
+ if (ndp->ni_vp == NULL)
+ nd->nd_repstat = nfsrv_opencheck(clientid,
+ stateidp, stp, NULL, nd, p, nd->nd_repstat);
+ if (!nd->nd_repstat) {
+ if (ndp->ni_vp == NULL) {
+ vrele(ndp->ni_startdir);
+ nd->nd_repstat = VOP_CREATE(ndp->ni_dvp,
+ &ndp->ni_vp, &ndp->ni_cnd, &nvap->na_vattr);
+ vput(ndp->ni_dvp);
+ nfsvno_relpathbuf(ndp);
+ if (!nd->nd_repstat) {
+ if (*exclusive_flagp) {
+ *exclusive_flagp = 0;
+ NFSVNO_ATTRINIT(nvap);
+ NFSBCOPY(cverf,
+ (caddr_t)&nvap->na_atime,
+ NFSX_VERF);
+ nd->nd_repstat = VOP_SETATTR(ndp->ni_vp,
+ &nvap->na_vattr, cred);
+ } else {
+ nfsrv_fixattr(nd, ndp->ni_vp, nvap,
+ aclp, p, attrbitp, exp);
+ }
+ }
+ vp = ndp->ni_vp;
+ } else {
+ if (ndp->ni_startdir)
+ vrele(ndp->ni_startdir);
+ nfsvno_relpathbuf(ndp);
+ vp = ndp->ni_vp;
+ if (create == NFSV4OPEN_CREATE) {
+ if (ndp->ni_dvp == vp)
+ vrele(ndp->ni_dvp);
+ else
+ vput(ndp->ni_dvp);
+ }
+ if (NFSVNO_ISSETSIZE(nvap) && vp->v_type == VREG) {
+ if (ndp->ni_cnd.cn_flags & RDONLY)
+ NFSVNO_SETEXRDONLY(&nes);
+ else
+ NFSVNO_EXINIT(&nes);
+ nd->nd_repstat = nfsvno_accchk(vp,
+ NFSV4ACE_ADDFILE, cred, &nes, p,
+ NFSACCCHK_NOOVERRIDE,NFSACCCHK_VPISLOCKED);
+ nd->nd_repstat = nfsrv_opencheck(clientid,
+ stateidp, stp, vp, nd, p, nd->nd_repstat);
+ if (!nd->nd_repstat) {
+ tempsize = nvap->na_size;
+ NFSVNO_ATTRINIT(nvap);
+ nvap->na_size = tempsize;
+ nd->nd_repstat = VOP_SETATTR(vp,
+ &nvap->na_vattr, cred);
+ }
+ } else if (vp->v_type == VREG) {
+ nd->nd_repstat = nfsrv_opencheck(clientid,
+ stateidp, stp, vp, nd, p, nd->nd_repstat);
+ }
+ }
+ } else {
+ if (ndp->ni_cnd.cn_flags & HASBUF)
+ nfsvno_relpathbuf(ndp);
+ if (ndp->ni_startdir && create == NFSV4OPEN_CREATE) {
+ vrele(ndp->ni_startdir);
+ if (ndp->ni_dvp == ndp->ni_vp)
+ vrele(ndp->ni_dvp);
+ else
+ vput(ndp->ni_dvp);
+ if (ndp->ni_vp)
+ vput(ndp->ni_vp);
+ }
+ }
+ *vpp = vp;
+}
+
+/*
+ * Updates the file rev and sets the mtime and ctime
+ * to the current clock time, returning the va_filerev and va_Xtime
+ * values.
+ */
+void
+nfsvno_updfilerev(struct vnode *vp, struct nfsvattr *nvap,
+ struct ucred *cred, struct thread *p)
+{
+ struct vattr va;
+
+ VATTR_NULL(&va);
+ getnanotime(&va.va_mtime);
+ (void) VOP_SETATTR(vp, &va, cred);
+ (void) nfsvno_getattr(vp, nvap, cred, p);
+}
+
+/*
+ * Glue routine to nfsv4_fillattr().
+ */
+int
+nfsvno_fillattr(struct nfsrv_descript *nd, struct vnode *vp,
+ struct nfsvattr *nvap, fhandle_t *fhp, int rderror, nfsattrbit_t *attrbitp,
+ struct ucred *cred, struct thread *p, int isdgram, int reterr)
+{
+ int error;
+
+ error = nfsv4_fillattr(nd, vp, NULL, &nvap->na_vattr, fhp, rderror,
+ attrbitp, cred, p, isdgram, reterr);
+ return (error);
+}
+
+/* Since the Readdir vnode ops vary, put the entire functions in here. */
+/*
+ * nfs readdir service
+ * - mallocs what it thinks is enough to read
+ * count rounded up to a multiple of DIRBLKSIZ <= NFS_MAXREADDIR
+ * - calls nfsvno_readdir()
+ * - loops around building the reply
+ * if the output generated exceeds count break out of loop
+ * The NFSM_CLGET macro is used here so that the reply will be packed
+ * tightly in mbuf clusters.
+ * - it only knows that it has encountered eof when the nfsvno_readdir()
+ * reads nothing
+ * - as such one readdir rpc will return eof false although you are there
+ * and then the next will return eof
+ * - it trims out records with d_fileno == 0
+ * this doesn't matter for Unix clients, but they might confuse clients
+ * for other os'.
+ * - it trims out records with d_type == DT_WHT
+ * these cannot be seen through NFS (unless we extend the protocol)
+ * NB: It is tempting to set eof to true if the nfsvno_readdir() reads less
+ * than requested, but this may not apply to all filesystems. For
+ * example, client NFS does not { although it is never remote mounted
+ * anyhow }
+ * The alternate call nfsrvd_readdirplus() does lookups as well.
+ * PS: The NFS protocol spec. does not clarify what the "count" byte
+ * argument is a count of.. just name strings and file id's or the
+ * entire reply rpc or ...
+ * I tried just file name and id sizes and it confused the Sun client,
+ * so I am using the full rpc size now. The "paranoia.." comment refers
+ * to including the status longwords that are not a part of the dir.
+ * "entry" structures, but are in the rpc.
+ */
+int
+nfsrvd_readdir(struct nfsrv_descript *nd, int isdgram,
+ struct vnode *vp, struct thread *p, struct nfsexstuff *exp)
+{
+ struct dirent *dp;
+ u_int32_t *tl;
+ int dirlen;
+ char *cpos, *cend, *rbuf;
+ struct nfsvattr at;
+ int nlen, error = 0, getret = 1;
+ int siz, cnt, fullsiz, eofflag, ncookies;
+ u_int64_t off, toff, verf;
+ u_long *cookies = NULL, *cookiep;
+ struct uio io;
+ struct iovec iv;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+ if (nd->nd_flag & ND_NFSV2) {
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ off = fxdr_unsigned(u_quad_t, *tl++);
+ } else {
+ NFSM_DISSECT(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
+ off = fxdr_hyper(tl);
+ tl += 2;
+ verf = fxdr_hyper(tl);
+ tl += 2;
+ }
+ toff = off;
+ cnt = fxdr_unsigned(int, *tl);
+ if (cnt > NFS_SRVMAXDATA(nd))
+ cnt = NFS_SRVMAXDATA(nd);
+ siz = ((cnt + DIRBLKSIZ - 1) & ~(DIRBLKSIZ - 1));
+ fullsiz = siz;
+ if (nd->nd_flag & ND_NFSV3) {
+ nd->nd_repstat = getret = nfsvno_getattr(vp, &at, nd->nd_cred,
+ p);
+#if 0
+ /*
+ * va_filerev is not sufficient as a cookie verifier,
+ * since it is not supposed to change when entries are
+ * removed/added unless that offset cookies returned to
+ * the client are no longer valid.
+ */
+ if (!nd->nd_repstat && toff && verf != at.na_filerev)
+ nd->nd_repstat = NFSERR_BAD_COOKIE;
+#endif
+ }
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_SEARCH,
+ nd->nd_cred, exp, p, NFSACCCHK_NOOVERRIDE,
+ NFSACCCHK_VPISLOCKED);
+ if (nd->nd_repstat) {
+ vput(vp);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+ NFSVOPUNLOCK(vp, 0, p);
+ MALLOC(rbuf, caddr_t, siz, M_TEMP, M_WAITOK);
+again:
+ eofflag = 0;
+ if (cookies) {
+ free((caddr_t)cookies, M_TEMP);
+ cookies = NULL;
+ }
+
+ iv.iov_base = rbuf;
+ iv.iov_len = siz;
+ io.uio_iov = &iv;
+ io.uio_iovcnt = 1;
+ io.uio_offset = (off_t)off;
+ io.uio_resid = siz;
+ io.uio_segflg = UIO_SYSSPACE;
+ io.uio_rw = UIO_READ;
+ io.uio_td = NULL;
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ nd->nd_repstat = VOP_READDIR(vp, &io, nd->nd_cred, &eofflag, &ncookies,
+ &cookies);
+ NFSVOPUNLOCK(vp, 0, p);
+ off = (u_int64_t)io.uio_offset;
+ if (io.uio_resid)
+ siz -= io.uio_resid;
+
+ if (!cookies && !nd->nd_repstat)
+ nd->nd_repstat = NFSERR_PERM;
+ if (nd->nd_flag & ND_NFSV3) {
+ getret = nfsvno_getattr(vp, &at, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = getret;
+ }
+
+ /*
+ * Handles the failed cases. nd->nd_repstat == 0 past here.
+ */
+ if (nd->nd_repstat) {
+ vrele(vp);
+ free((caddr_t)rbuf, M_TEMP);
+ if (cookies)
+ free((caddr_t)cookies, M_TEMP);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+ /*
+ * If nothing read, return eof
+ * rpc reply
+ */
+ if (siz == 0) {
+ vrele(vp);
+ if (nd->nd_flag & ND_NFSV2) {
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ } else {
+ nfsrv_postopattr(nd, getret, &at);
+ NFSM_BUILD(tl, u_int32_t *, 4 * NFSX_UNSIGNED);
+ txdr_hyper(at.na_filerev, tl);
+ tl += 2;
+ }
+ *tl++ = newnfs_false;
+ *tl = newnfs_true;
+ FREE((caddr_t)rbuf, M_TEMP);
+ FREE((caddr_t)cookies, M_TEMP);
+ return (0);
+ }
+
+ /*
+ * Check for degenerate cases of nothing useful read.
+ * If so go try again
+ */
+ cpos = rbuf;
+ cend = rbuf + siz;
+ dp = (struct dirent *)cpos;
+ cookiep = cookies;
+
+ /*
+ * For some reason FreeBSD's ufs_readdir() chooses to back the
+ * directory offset up to a block boundary, so it is necessary to
+ * skip over the records that precede the requested offset. This
+ * requires the assumption that file offset cookies monotonically
+ * increase.
+ */
+ while (cpos < cend && ncookies > 0 &&
+ (dp->d_fileno == 0 || dp->d_type == DT_WHT ||
+ ((u_quad_t)(*cookiep)) <= toff)) {
+ cpos += dp->d_reclen;
+ dp = (struct dirent *)cpos;
+ cookiep++;
+ ncookies--;
+ }
+ if (cpos >= cend || ncookies == 0) {
+ siz = fullsiz;
+ toff = off;
+ goto again;
+ }
+
+ /*
+ * dirlen is the size of the reply, including all XDR and must
+ * not exceed cnt. For NFSv2, RFC1094 didn't clearly indicate
+ * if the XDR should be included in "count", but to be safe, we do.
+ * (Include the two booleans at the end of the reply in dirlen now.)
+ */
+ if (nd->nd_flag & ND_NFSV3) {
+ nfsrv_postopattr(nd, getret, &at);
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ txdr_hyper(at.na_filerev, tl);
+ dirlen = NFSX_V3POSTOPATTR + NFSX_VERF + 2 * NFSX_UNSIGNED;
+ } else {
+ dirlen = 2 * NFSX_UNSIGNED;
+ }
+
+ /* Loop through the records and build reply */
+ while (cpos < cend && ncookies > 0) {
+ nlen = dp->d_namlen;
+ if (dp->d_fileno != 0 && dp->d_type != DT_WHT &&
+ nlen <= NFS_MAXNAMLEN) {
+ if (nd->nd_flag & ND_NFSV3)
+ dirlen += (6*NFSX_UNSIGNED + NFSM_RNDUP(nlen));
+ else
+ dirlen += (4*NFSX_UNSIGNED + NFSM_RNDUP(nlen));
+ if (dirlen > cnt) {
+ eofflag = 0;
+ break;
+ }
+
+ /*
+ * Build the directory record xdr from
+ * the dirent entry.
+ */
+ if (nd->nd_flag & ND_NFSV3) {
+ NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
+ *tl++ = newnfs_true;
+ *tl++ = 0;
+ } else {
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ *tl++ = newnfs_true;
+ }
+ *tl = txdr_unsigned(dp->d_fileno);
+ (void) nfsm_strtom(nd, dp->d_name, nlen);
+ if (nd->nd_flag & ND_NFSV3) {
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ *tl++ = 0;
+ } else
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl = txdr_unsigned(*cookiep);
+ }
+ cpos += dp->d_reclen;
+ dp = (struct dirent *)cpos;
+ cookiep++;
+ ncookies--;
+ }
+ if (cpos < cend)
+ eofflag = 0;
+ vrele(vp);
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ *tl++ = newnfs_false;
+ if (eofflag)
+ *tl = newnfs_true;
+ else
+ *tl = newnfs_false;
+ FREE((caddr_t)rbuf, M_TEMP);
+ FREE((caddr_t)cookies, M_TEMP);
+ return (0);
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * Readdirplus for V3 and Readdir for V4.
+ */
+int
+nfsrvd_readdirplus(struct nfsrv_descript *nd, int isdgram,
+ struct vnode *vp, struct thread *p, struct nfsexstuff *exp)
+{
+ struct dirent *dp;
+ u_int32_t *tl;
+ int dirlen;
+ char *cpos, *cend, *rbuf;
+ struct vnode *nvp;
+ fhandle_t nfh;
+ struct nfsvattr nva, at, *nvap = &nva;
+ struct mbuf *mb0, *mb1;
+ struct nfsreferral *refp;
+ int nlen, r, error = 0, getret = 1, vgetret;
+ int siz, cnt, fullsiz, eofflag, ncookies, entrycnt;
+ caddr_t bpos0, bpos1;
+ u_int64_t off, toff, verf;
+ u_long *cookies = NULL, *cookiep;
+ nfsattrbit_t attrbits, rderrbits, savbits;
+ struct uio io;
+ struct iovec iv;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+ NFSM_DISSECT(tl, u_int32_t *, 6 * NFSX_UNSIGNED);
+ off = fxdr_hyper(tl);
+ toff = off;
+ tl += 2;
+ verf = fxdr_hyper(tl);
+ tl += 2;
+ siz = fxdr_unsigned(int, *tl++);
+ cnt = fxdr_unsigned(int, *tl);
+
+ /*
+ * Use the server's maximum data transfer size as the upper bound
+ * on reply datalen.
+ */
+ if (cnt > NFS_SRVMAXDATA(nd))
+ cnt = NFS_SRVMAXDATA(nd);
+
+ /*
+ * siz is a "hint" of how much directory information (name, fileid,
+ * cookie) should be in the reply. At least one client "hints" 0,
+ * so I set it to cnt for that case. I also round it up to the
+ * next multiple of DIRBLKSIZ.
+ */
+ if (siz == 0)
+ siz = cnt;
+ siz = ((siz + DIRBLKSIZ - 1) & ~(DIRBLKSIZ - 1));
+
+ if (nd->nd_flag & ND_NFSV4) {
+ error = nfsrv_getattrbits(nd, &attrbits, NULL, NULL);
+ if (error)
+ goto nfsmout;
+ NFSSET_ATTRBIT(&savbits, &attrbits);
+ NFSCLRNOTFILLABLE_ATTRBIT(&attrbits);
+ NFSZERO_ATTRBIT(&rderrbits);
+ NFSSETBIT_ATTRBIT(&rderrbits, NFSATTRBIT_RDATTRERROR);
+ } else {
+ NFSZERO_ATTRBIT(&attrbits);
+ }
+ fullsiz = siz;
+ nd->nd_repstat = getret = nfsvno_getattr(vp, &at, nd->nd_cred, p);
+ if (!nd->nd_repstat) {
+ if (off && verf != at.na_filerev) {
+ /*
+ * va_filerev is not sufficient as a cookie verifier,
+ * since it is not supposed to change when entries are
+ * removed/added unless that offset cookies returned to
+ * the client are no longer valid.
+ */
+#if 0
+ if (nd->nd_flag & ND_NFSV4) {
+ nd->nd_repstat = NFSERR_NOTSAME;
+ } else {
+ nd->nd_repstat = NFSERR_BAD_COOKIE;
+ }
+#endif
+ } else if ((nd->nd_flag & ND_NFSV4) && off == 0 && verf != 0) {
+ nd->nd_repstat = NFSERR_BAD_COOKIE;
+ }
+ }
+ if (!nd->nd_repstat && vp->v_type != VDIR)
+ nd->nd_repstat = NFSERR_NOTDIR;
+ if (!nd->nd_repstat && cnt == 0)
+ nd->nd_repstat = NFSERR_TOOSMALL;
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_SEARCH,
+ nd->nd_cred, exp, p, NFSACCCHK_NOOVERRIDE,
+ NFSACCCHK_VPISLOCKED);
+ if (nd->nd_repstat) {
+ vput(vp);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+
+ NFSVOPUNLOCK(vp, 0, p);
+
+ MALLOC(rbuf, caddr_t, siz, M_TEMP, M_WAITOK);
+again:
+ eofflag = 0;
+ if (cookies) {
+ free((caddr_t)cookies, M_TEMP);
+ cookies = NULL;
+ }
+
+ iv.iov_base = rbuf;
+ iv.iov_len = siz;
+ io.uio_iov = &iv;
+ io.uio_iovcnt = 1;
+ io.uio_offset = (off_t)off;
+ io.uio_resid = siz;
+ io.uio_segflg = UIO_SYSSPACE;
+ io.uio_rw = UIO_READ;
+ io.uio_td = NULL;
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ nd->nd_repstat = VOP_READDIR(vp, &io, nd->nd_cred, &eofflag, &ncookies,
+ &cookies);
+ NFSVOPUNLOCK(vp, 0, p);
+ off = (u_int64_t)io.uio_offset;
+ if (io.uio_resid)
+ siz -= io.uio_resid;
+
+ getret = nfsvno_getattr(vp, &at, nd->nd_cred, p);
+
+ if (!cookies && !nd->nd_repstat)
+ nd->nd_repstat = NFSERR_PERM;
+ if (!nd->nd_repstat)
+ nd->nd_repstat = getret;
+ if (nd->nd_repstat) {
+ vrele(vp);
+ if (cookies)
+ free((caddr_t)cookies, M_TEMP);
+ free((caddr_t)rbuf, M_TEMP);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+ /*
+ * If nothing read, return eof
+ * rpc reply
+ */
+ if (siz == 0) {
+ vrele(vp);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, getret, &at);
+ NFSM_BUILD(tl, u_int32_t *, 4 * NFSX_UNSIGNED);
+ txdr_hyper(at.na_filerev, tl);
+ tl += 2;
+ *tl++ = newnfs_false;
+ *tl = newnfs_true;
+ free((caddr_t)cookies, M_TEMP);
+ free((caddr_t)rbuf, M_TEMP);
+ return (0);
+ }
+
+ /*
+ * Check for degenerate cases of nothing useful read.
+ * If so go try again
+ */
+ cpos = rbuf;
+ cend = rbuf + siz;
+ dp = (struct dirent *)cpos;
+ cookiep = cookies;
+
+ /*
+ * For some reason FreeBSD's ufs_readdir() chooses to back the
+ * directory offset up to a block boundary, so it is necessary to
+ * skip over the records that precede the requested offset. This
+ * requires the assumption that file offset cookies monotonically
+ * increase.
+ */
+ while (cpos < cend && ncookies > 0 &&
+ (dp->d_fileno == 0 || dp->d_type == DT_WHT ||
+ ((u_quad_t)(*cookiep)) <= toff ||
+ ((nd->nd_flag & ND_NFSV4) &&
+ ((dp->d_namlen == 1 && dp->d_name[0] == '.') ||
+ (dp->d_namlen==2 && dp->d_name[0]=='.' && dp->d_name[1]=='.'))))) {
+ cpos += dp->d_reclen;
+ dp = (struct dirent *)cpos;
+ cookiep++;
+ ncookies--;
+ }
+ if (cpos >= cend || ncookies == 0) {
+ siz = fullsiz;
+ toff = off;
+ goto again;
+ }
+
+ /*
+ * Probe one of the directory entries to see if the filesystem
+ * supports VGET for NFSv3. For NFSv4, it will return an
+ * error later, if attributes are required.
+ * (To be honest, most if not all NFSv4 clients will require
+ * attributes, but??)
+ */
+ if ((nd->nd_flag & ND_NFSV3)) {
+ vgetret = VFS_VGET(vp->v_mount, dp->d_fileno, LK_EXCLUSIVE,
+ &nvp);
+ if (vgetret != 0) {
+ if (vgetret == EOPNOTSUPP)
+ nd->nd_repstat = NFSERR_NOTSUPP;
+ else
+ nd->nd_repstat = NFSERR_SERVERFAULT;
+ vrele(vp);
+ if (cookies)
+ free((caddr_t)cookies, M_TEMP);
+ free((caddr_t)rbuf, M_TEMP);
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+ if (!vgetret)
+ vput(nvp);
+ nvp = NULL;
+ }
+
+ /*
+ * Save this position, in case there is an error before one entry
+ * is created.
+ */
+ mb0 = nd->nd_mb;
+ bpos0 = nd->nd_bpos;
+
+ /*
+ * Fill in the first part of the reply.
+ * dirlen is the reply length in bytes and cannot exceed cnt.
+ * (Include the two booleans at the end of the reply in dirlen now,
+ * so we recognize when we have exceeded cnt.)
+ */
+ if (nd->nd_flag & ND_NFSV3) {
+ dirlen = NFSX_V3POSTOPATTR + NFSX_VERF + 2 * NFSX_UNSIGNED;
+ nfsrv_postopattr(nd, getret, &at);
+ } else {
+ dirlen = NFSX_VERF + 2 * NFSX_UNSIGNED;
+ }
+ NFSM_BUILD(tl, u_int32_t *, NFSX_VERF);
+ txdr_hyper(at.na_filerev, tl);
+
+ /*
+ * Save this position, in case there is an empty reply needed.
+ */
+ mb1 = nd->nd_mb;
+ bpos1 = nd->nd_bpos;
+
+ /* Loop through the records and build reply */
+ entrycnt = 0;
+ while (cpos < cend && ncookies > 0 && dirlen < cnt) {
+ nlen = dp->d_namlen;
+ if (dp->d_fileno != 0 && dp->d_type != DT_WHT &&
+ nlen <= NFS_MAXNAMLEN &&
+ ((nd->nd_flag & ND_NFSV3) || nlen > 2 ||
+ (nlen==2 && (dp->d_name[0]!='.' || dp->d_name[1]!='.'))
+ || (nlen == 1 && dp->d_name[0] != '.'))) {
+ /*
+ * Save the current position in the reply, in case
+ * this entry exceeds cnt.
+ */
+ mb1 = nd->nd_mb;
+ bpos1 = nd->nd_bpos;
+
+ /*
+ * For readdir_and_lookup get the vnode using
+ * the file number.
+ */
+ nvp = NULL;
+ refp = NULL;
+ r = 0;
+ if ((nd->nd_flag & ND_NFSV3) ||
+ NFSNONZERO_ATTRBIT(&savbits)) {
+ if (nd->nd_flag & ND_NFSV4)
+ refp = nfsv4root_getreferral(NULL,
+ vp, dp->d_fileno);
+ if (refp == NULL)
+ r = VFS_VGET(vp->v_mount, dp->d_fileno,
+ LK_EXCLUSIVE, &nvp);
+ if (!r) {
+ if (refp == NULL &&
+ ((nd->nd_flag & ND_NFSV3) ||
+ NFSNONZERO_ATTRBIT(&attrbits))) {
+ r = nfsvno_getfh(nvp, &nfh, p);
+ if (!r)
+ r = nfsvno_getattr(nvp, nvap,
+ nd->nd_cred, p);
+ }
+ } else {
+ nvp = NULL;
+ }
+ if (r) {
+ if (!NFSISSET_ATTRBIT(&attrbits,
+ NFSATTRBIT_RDATTRERROR)) {
+ if (nvp != NULL)
+ vput(nvp);
+ nd->nd_repstat = r;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Build the directory record xdr
+ */
+ if (nd->nd_flag & ND_NFSV3) {
+ NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
+ *tl++ = newnfs_true;
+ *tl++ = 0;
+ *tl = txdr_unsigned(dp->d_fileno);
+ dirlen += nfsm_strtom(nd, dp->d_name, nlen);
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ *tl++ = 0;
+ *tl = txdr_unsigned(*cookiep);
+ nfsrv_postopattr(nd, 0, nvap);
+ dirlen += nfsm_fhtom(nd,(u_int8_t *)&nfh,0,1);
+ dirlen += (5*NFSX_UNSIGNED+NFSX_V3POSTOPATTR);
+ if (nvp != NULL)
+ vput(nvp);
+ } else {
+ NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
+ *tl++ = newnfs_true;
+ *tl++ = 0;
+ *tl = txdr_unsigned(*cookiep);
+ dirlen += nfsm_strtom(nd, dp->d_name, nlen);
+ if (nvp != NULL)
+ NFSVOPUNLOCK(nvp, 0, p);
+ if (refp != NULL) {
+ dirlen += nfsrv_putreferralattr(nd,
+ &savbits, refp, 0,
+ &nd->nd_repstat);
+ if (nd->nd_repstat) {
+ if (nvp != NULL)
+ vrele(nvp);
+ break;
+ }
+ } else if (r) {
+ dirlen += nfsvno_fillattr(nd, nvp, nvap,
+ &nfh, r, &rderrbits, nd->nd_cred,
+ p, isdgram, 0);
+ } else {
+ dirlen += nfsvno_fillattr(nd, nvp, nvap,
+ &nfh, r, &attrbits, nd->nd_cred,
+ p, isdgram, 0);
+ }
+ if (nvp != NULL)
+ vrele(nvp);
+ dirlen += (3 * NFSX_UNSIGNED);
+ }
+ if (dirlen <= cnt)
+ entrycnt++;
+ }
+ cpos += dp->d_reclen;
+ dp = (struct dirent *)cpos;
+ cookiep++;
+ ncookies--;
+ }
+ vrele(vp);
+
+ /*
+ * If dirlen > cnt, we must strip off the last entry. If that
+ * results in an empty reply, report NFSERR_TOOSMALL.
+ */
+ if (dirlen > cnt || nd->nd_repstat) {
+ if (!nd->nd_repstat && entrycnt == 0)
+ nd->nd_repstat = NFSERR_TOOSMALL;
+ if (nd->nd_repstat)
+ newnfs_trimtrailing(nd, mb0, bpos0);
+ else
+ newnfs_trimtrailing(nd, mb1, bpos1);
+ eofflag = 0;
+ } else if (cpos < cend)
+ eofflag = 0;
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ *tl++ = newnfs_false;
+ if (eofflag)
+ *tl = newnfs_true;
+ else
+ *tl = newnfs_false;
+ }
+ FREE((caddr_t)cookies, M_TEMP);
+ FREE((caddr_t)rbuf, M_TEMP);
+ return (0);
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * Get the settable attributes out of the mbuf list.
+ * (Return 0 or EBADRPC)
+ */
+int
+nfsrv_sattr(struct nfsrv_descript *nd, struct nfsvattr *nvap,
+ nfsattrbit_t *attrbitp, NFSACL_T *aclp, struct thread *p)
+{
+ u_int32_t *tl;
+ struct nfsv2_sattr *sp;
+ struct timeval curtime;
+ int error = 0, toclient = 0;
+
+ switch (nd->nd_flag & (ND_NFSV2 | ND_NFSV3 | ND_NFSV4)) {
+ case ND_NFSV2:
+ NFSM_DISSECT(sp, struct nfsv2_sattr *, NFSX_V2SATTR);
+ /*
+ * Some old clients didn't fill in the high order 16bits.
+ * --> check the low order 2 bytes for 0xffff
+ */
+ if ((fxdr_unsigned(int, sp->sa_mode) & 0xffff) != 0xffff)
+ nvap->na_mode = nfstov_mode(sp->sa_mode);
+ if (sp->sa_uid != newnfs_xdrneg1)
+ nvap->na_uid = fxdr_unsigned(uid_t, sp->sa_uid);
+ if (sp->sa_gid != newnfs_xdrneg1)
+ nvap->na_gid = fxdr_unsigned(gid_t, sp->sa_gid);
+ if (sp->sa_size != newnfs_xdrneg1)
+ nvap->na_size = fxdr_unsigned(u_quad_t, sp->sa_size);
+ if (sp->sa_atime.nfsv2_sec != newnfs_xdrneg1) {
+#ifdef notyet
+ fxdr_nfsv2time(&sp->sa_atime, &nvap->na_atime);
+#else
+ nvap->na_atime.tv_sec =
+ fxdr_unsigned(u_int32_t,sp->sa_atime.nfsv2_sec);
+ nvap->na_atime.tv_nsec = 0;
+#endif
+ }
+ if (sp->sa_mtime.nfsv2_sec != newnfs_xdrneg1)
+ fxdr_nfsv2time(&sp->sa_mtime, &nvap->na_mtime);
+ break;
+ case ND_NFSV3:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (*tl == newnfs_true) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ nvap->na_mode = nfstov_mode(*tl);
+ }
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (*tl == newnfs_true) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ nvap->na_uid = fxdr_unsigned(uid_t, *tl);
+ }
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (*tl == newnfs_true) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ nvap->na_gid = fxdr_unsigned(gid_t, *tl);
+ }
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (*tl == newnfs_true) {
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ nvap->na_size = fxdr_hyper(tl);
+ }
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ switch (fxdr_unsigned(int, *tl)) {
+ case NFSV3SATTRTIME_TOCLIENT:
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ fxdr_nfsv3time(tl, &nvap->na_atime);
+ toclient = 1;
+ break;
+ case NFSV3SATTRTIME_TOSERVER:
+ NFSGETTIME(&curtime);
+ nvap->na_atime.tv_sec = curtime.tv_sec;
+ nvap->na_atime.tv_nsec = curtime.tv_usec * 1000;
+ nvap->na_vaflags |= VA_UTIMES_NULL;
+ break;
+ };
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ switch (fxdr_unsigned(int, *tl)) {
+ case NFSV3SATTRTIME_TOCLIENT:
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ fxdr_nfsv3time(tl, &nvap->na_mtime);
+ nvap->na_vaflags &= ~VA_UTIMES_NULL;
+ break;
+ case NFSV3SATTRTIME_TOSERVER:
+ NFSGETTIME(&curtime);
+ nvap->na_mtime.tv_sec = curtime.tv_sec;
+ nvap->na_mtime.tv_nsec = curtime.tv_usec * 1000;
+ if (!toclient)
+ nvap->na_vaflags |= VA_UTIMES_NULL;
+ break;
+ };
+ break;
+ case ND_NFSV4:
+ error = nfsv4_sattr(nd, nvap, attrbitp, aclp, p);
+ };
+nfsmout:
+ return (error);
+}
+
+/*
+ * Handle the setable attributes for V4.
+ * Returns NFSERR_BADXDR if it can't be parsed, 0 otherwise.
+ */
+int
+nfsv4_sattr(struct nfsrv_descript *nd, struct nfsvattr *nvap,
+ nfsattrbit_t *attrbitp, NFSACL_T *aclp, struct thread *p)
+{
+ u_int32_t *tl;
+ int attrsum = 0;
+ int i, j;
+ int error, attrsize, bitpos, aclsize, aceerr, retnotsup = 0;
+ int toclient = 0;
+ u_char *cp, namestr[NFSV4_SMALLSTR + 1];
+ uid_t uid;
+ gid_t gid;
+ struct timeval curtime;
+
+ error = nfsrv_getattrbits(nd, attrbitp, NULL, &retnotsup);
+ if (error)
+ return (error);
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ attrsize = fxdr_unsigned(int, *tl);
+
+ /*
+ * Loop around getting the setable attributes. If an unsupported
+ * one is found, set nd_repstat == NFSERR_ATTRNOTSUPP and return.
+ */
+ if (retnotsup) {
+ nd->nd_repstat = NFSERR_ATTRNOTSUPP;
+ bitpos = NFSATTRBIT_MAX;
+ } else {
+ bitpos = 0;
+ }
+ for (; bitpos < NFSATTRBIT_MAX; bitpos++) {
+ if (attrsum > attrsize) {
+ error = NFSERR_BADXDR;
+ goto nfsmout;
+ }
+ if (NFSISSET_ATTRBIT(attrbitp, bitpos))
+ switch (bitpos) {
+ case NFSATTRBIT_SIZE:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_HYPER);
+ nvap->na_size = fxdr_hyper(tl);
+ attrsum += NFSX_HYPER;
+ break;
+ case NFSATTRBIT_ACL:
+ error = nfsrv_dissectacl(nd, aclp, &aceerr, &aclsize,
+ p);
+ if (error)
+ goto nfsmout;
+ if (aceerr && !nd->nd_repstat)
+ nd->nd_repstat = NFSERR_ATTRNOTSUPP;
+ attrsum += aclsize;
+ break;
+ case NFSATTRBIT_ARCHIVE:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = NFSERR_ATTRNOTSUPP;
+ attrsum += NFSX_UNSIGNED;
+ break;
+ case NFSATTRBIT_HIDDEN:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = NFSERR_ATTRNOTSUPP;
+ attrsum += NFSX_UNSIGNED;
+ break;
+ case NFSATTRBIT_MIMETYPE:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ i = fxdr_unsigned(int, *tl);
+ error = nfsm_advance(nd, NFSM_RNDUP(i), -1);
+ if (error)
+ goto nfsmout;
+ if (!nd->nd_repstat)
+ nd->nd_repstat = NFSERR_ATTRNOTSUPP;
+ attrsum += (NFSX_UNSIGNED + NFSM_RNDUP(i));
+ break;
+ case NFSATTRBIT_MODE:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ nvap->na_mode = nfstov_mode(*tl);
+ attrsum += NFSX_UNSIGNED;
+ break;
+ case NFSATTRBIT_OWNER:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ j = fxdr_unsigned(int, *tl);
+ if (j < 0)
+ return (NFSERR_BADXDR);
+ if (j > NFSV4_SMALLSTR)
+ cp = malloc(j + 1, M_NFSSTRING, M_WAITOK);
+ else
+ cp = namestr;
+ error = nfsrv_mtostr(nd, cp, j);
+ if (error) {
+ if (j > NFSV4_SMALLSTR)
+ free(cp, M_NFSSTRING);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsv4_strtouid(cp,j,&uid,p);
+ if (!nd->nd_repstat)
+ nvap->na_uid = uid;
+ }
+ if (j > NFSV4_SMALLSTR)
+ free(cp, M_NFSSTRING);
+ attrsum += (NFSX_UNSIGNED + NFSM_RNDUP(j));
+ break;
+ case NFSATTRBIT_OWNERGROUP:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ j = fxdr_unsigned(int, *tl);
+ if (j < 0)
+ return (NFSERR_BADXDR);
+ if (j > NFSV4_SMALLSTR)
+ cp = malloc(j + 1, M_NFSSTRING, M_WAITOK);
+ else
+ cp = namestr;
+ error = nfsrv_mtostr(nd, cp, j);
+ if (error) {
+ if (j > NFSV4_SMALLSTR)
+ free(cp, M_NFSSTRING);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsv4_strtogid(cp,j,&gid,p);
+ if (!nd->nd_repstat)
+ nvap->na_gid = gid;
+ }
+ if (j > NFSV4_SMALLSTR)
+ free(cp, M_NFSSTRING);
+ attrsum += (NFSX_UNSIGNED + NFSM_RNDUP(j));
+ break;
+ case NFSATTRBIT_SYSTEM:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = NFSERR_ATTRNOTSUPP;
+ attrsum += NFSX_UNSIGNED;
+ break;
+ case NFSATTRBIT_TIMEACCESSSET:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ attrsum += NFSX_UNSIGNED;
+ if (fxdr_unsigned(int, *tl)==NFSV4SATTRTIME_TOCLIENT) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_V4TIME);
+ fxdr_nfsv4time(tl, &nvap->na_atime);
+ toclient = 1;
+ attrsum += NFSX_V4TIME;
+ } else {
+ NFSGETTIME(&curtime);
+ nvap->na_atime.tv_sec = curtime.tv_sec;
+ nvap->na_atime.tv_nsec = curtime.tv_usec * 1000;
+ nvap->na_vaflags |= VA_UTIMES_NULL;
+ }
+ break;
+ case NFSATTRBIT_TIMEBACKUP:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_V4TIME);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = NFSERR_ATTRNOTSUPP;
+ attrsum += NFSX_V4TIME;
+ break;
+ case NFSATTRBIT_TIMECREATE:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_V4TIME);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = NFSERR_ATTRNOTSUPP;
+ attrsum += NFSX_V4TIME;
+ break;
+ case NFSATTRBIT_TIMEMODIFYSET:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ attrsum += NFSX_UNSIGNED;
+ if (fxdr_unsigned(int, *tl)==NFSV4SATTRTIME_TOCLIENT) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_V4TIME);
+ fxdr_nfsv4time(tl, &nvap->na_mtime);
+ nvap->na_vaflags &= ~VA_UTIMES_NULL;
+ attrsum += NFSX_V4TIME;
+ } else {
+ NFSGETTIME(&curtime);
+ nvap->na_mtime.tv_sec = curtime.tv_sec;
+ nvap->na_mtime.tv_nsec = curtime.tv_usec * 1000;
+ if (!toclient)
+ nvap->na_vaflags |= VA_UTIMES_NULL;
+ }
+ break;
+ default:
+ nd->nd_repstat = NFSERR_ATTRNOTSUPP;
+ /*
+ * set bitpos so we drop out of the loop.
+ */
+ bitpos = NFSATTRBIT_MAX;
+ break;
+ };
+ }
+
+ /*
+ * some clients pad the attrlist, so we need to skip over the
+ * padding.
+ */
+ if (attrsum > attrsize) {
+ error = NFSERR_BADXDR;
+ } else {
+ attrsize = NFSM_RNDUP(attrsize);
+ if (attrsum < attrsize)
+ error = nfsm_advance(nd, attrsize - attrsum, -1);
+ }
+nfsmout:
+ return (error);
+}
+
+/*
+ * Check/setup export credentials.
+ */
+int
+nfsd_excred(struct nfsrv_descript *nd, struct nfsexstuff *exp,
+ struct ucred *credanon)
+{
+ int i;
+ int error = 0;
+
+ /*
+ * Check/setup credentials.
+ */
+ if (nd->nd_flag & ND_GSS)
+ exp->nes_exflag &= ~(MNT_EXGSSONLY | MNT_EXPORTANON);
+
+ /*
+ * For AUTH_SYS, check to see if it is allowed.
+ * RFC2623 suggests that the NFSv3 Fsinfo RPC be allowed to
+ * AUTH_NONE or AUTH_SYS for file systems requiring RPCSEC_GSS.
+ */
+ if (NFSVNO_EXGSSONLY(exp) &&
+ nd->nd_procnum != NFSPROC_FSINFO) {
+ if (nd->nd_flag & ND_NFSV4)
+ error = NFSERR_WRONGSEC;
+ else
+ error = (NFSERR_AUTHERR | AUTH_TOOWEAK);
+ return (error);
+ }
+
+ /*
+ * Check to see if the file system is exported V4 only.
+ */
+ if (NFSVNO_EXV4ONLY(exp) && !(nd->nd_flag & ND_NFSV4))
+ return (NFSERR_PROGNOTV4);
+
+ /*
+ * Now, map the user credentials.
+ * (Note that ND_AUTHNONE will only be set for an NFSv3
+ * Fsinfo RPC. If set for anything else, this code might need
+ * to change.)
+ */
+ if (NFSVNO_EXPORTED(exp) &&
+ ((!(nd->nd_flag & ND_GSS) && nd->nd_cred->cr_uid == 0) ||
+ NFSVNO_EXPORTANON(exp) ||
+ (nd->nd_flag & ND_AUTHNONE))) {
+ nd->nd_cred->cr_uid = credanon->cr_uid;
+ nd->nd_cred->cr_gid = credanon->cr_gid;
+ for (i = 0; i < credanon->cr_ngroups && i < NGROUPS; i++)
+ nd->nd_cred->cr_groups[i] = credanon->cr_groups[i];
+ nd->nd_cred->cr_ngroups = i;
+ }
+ return (0);
+}
+
+/*
+ * Check exports.
+ */
+int
+nfsvno_checkexp(struct mount *mp, struct sockaddr *nam, struct nfsexstuff *exp,
+ struct ucred **credp)
+{
+ int error;
+ int numsecflavor, *secflavors;
+
+ error = VFS_CHECKEXP(mp, nam, &exp->nes_exflag, credp,
+ &numsecflavor, &secflavors);
+ if (error && nfs_rootfhset) {
+ exp->nes_exflag = 0;
+ error = 0;
+ }
+ return (error);
+}
+
+/*
+ * Get a vnode for a file handle and export stuff.
+ */
+int
+nfsvno_fhtovp(struct mount *mp, fhandle_t *fhp, struct sockaddr *nam,
+ struct vnode **vpp, struct nfsexstuff *exp, struct ucred **credp)
+{
+ int error;
+ int numsecflavor, *secflavors;
+
+ error = VFS_FHTOVP(mp, &fhp->fh_fid, vpp);
+ if (nam && !error) {
+ error = VFS_CHECKEXP(mp, nam, &exp->nes_exflag, credp,
+ &numsecflavor, &secflavors);
+ if (error) {
+ if (nfs_rootfhset) {
+ exp->nes_exflag = 0;
+ error = 0;
+ } else {
+ vput(*vpp);
+ }
+ }
+ }
+ return (error);
+}
+
+/*
+ * Do the pathconf vnode op.
+ */
+int
+nfsvno_pathconf(struct vnode *vp, int flag, register_t *retf,
+ struct ucred *cred, struct thread *p)
+{
+ int error;
+
+ error = VOP_PATHCONF(vp, flag, retf);
+ return (error);
+}
+
+/*
+ * nfsd_fhtovp() - convert a fh to a vnode ptr
+ * - look up fsid in mount list (if not found ret error)
+ * - get vp and export rights by calling nfsvno_fhtovp()
+ * - if cred->cr_uid == 0 or MNT_EXPORTANON set it to credanon
+ * for AUTH_SYS
+ * Also handle getting the Giant lock for the file system,
+ * as required:
+ * - if same mount point as *mpp
+ * do nothing
+ * else if *mpp == NULL
+ * if already locked
+ * leave it locked
+ * else
+ * call VFS_LOCK_GIANT()
+ * else
+ * if already locked
+ * unlock Giant
+ * call VFS_LOCK_GIANT()
+ */
+void
+nfsd_fhtovp(struct nfsrv_descript *nd, struct nfsrvfh *nfp,
+ struct vnode **vpp, struct nfsexstuff *exp,
+ struct mount **mpp, int startwrite, struct thread *p)
+{
+ struct mount *mp;
+ struct ucred *credanon;
+ fhandle_t *fhp;
+
+ fhp = (fhandle_t *)nfp->nfsrvfh_data;
+ /*
+ * Check for the special case of the nfsv4root_fh.
+ */
+ mp = vfs_getvfs(&fhp->fh_fsid);
+ if (!mp) {
+ *vpp = NULL;
+ nd->nd_repstat = ESTALE;
+ if (*mpp && exp->nes_vfslocked)
+ VFS_UNLOCK_GIANT(*mpp);
+ *mpp = NULL;
+ exp->nes_vfslocked = 0;
+ return;
+ }
+
+ /*
+ * Now, handle Giant for the file system.
+ */
+ if (*mpp != NULL && *mpp != mp && exp->nes_vfslocked) {
+ VFS_UNLOCK_GIANT(*mpp);
+ exp->nes_vfslocked = 0;
+ }
+ if (!exp->nes_vfslocked && *mpp != mp)
+ exp->nes_vfslocked = VFS_LOCK_GIANT(mp);
+
+ *mpp = mp;
+ if (startwrite)
+ vn_start_write(NULL, mpp, V_WAIT);
+
+ nd->nd_repstat = nfsvno_fhtovp(mp, fhp, nd->nd_nam, vpp, exp,
+ &credanon);
+
+ /*
+ * For NFSv4 without a pseudo root fs, unexported file handles
+ * can be returned, so that Lookup works everywhere.
+ */
+ if (!nd->nd_repstat && exp->nes_exflag == 0 &&
+ !(nd->nd_flag & ND_NFSV4)) {
+ vput(*vpp);
+ nd->nd_repstat = EACCES;
+ }
+
+ /*
+ * Personally, I've never seen any point in requiring a
+ * reserved port#, since only in the rare case where the
+ * clients are all boxes with secure system priviledges,
+ * does it provide any enhanced security, but... some people
+ * believe it to be useful and keep putting this code back in.
+ * (There is also some "security checker" out there that
+ * complains if the nfs server doesn't enforce this.)
+ * However, note the following:
+ * RFC3530 (NFSv4) specifies that a reserved port# not be
+ * required.
+ * RFC2623 recommends that, if a reserved port# is checked for,
+ * that there be a way to turn that off--> ifdef'd.
+ */
+#ifdef NFS_REQRSVPORT
+ if (!nd->nd_repstat) {
+ struct sockaddr_in *saddr;
+ struct sockaddr_in6 *saddr6;
+ saddr = NFSSOCKADDR(nd->nd_nam, struct sockaddr_in *);
+ saddr6 = NFSSOCKADDR(nd->nd_nam, struct sockaddr_in6 *);
+ if (!(nd->nd_flag & ND_NFSV4) &&
+ ((saddr->sin_family == AF_INET &&
+ ntohs(saddr->sin_port) >= IPPORT_RESERVED) ||
+ (saddr6->sin6_family == AF_INET6 &&
+ ntohs(saddr6->sin6_port) >= IPPORT_RESERVED))) {
+ vput(*vpp);
+ nd->nd_repstat = (NFSERR_AUTHERR | AUTH_TOOWEAK);
+ }
+ }
+#endif /* NFS_REQRSVPORT */
+
+ /*
+ * Check/setup credentials.
+ */
+ if (!nd->nd_repstat) {
+ nd->nd_saveduid = nd->nd_cred->cr_uid;
+ nd->nd_repstat = nfsd_excred(nd, exp, credanon);
+ if (nd->nd_repstat)
+ vput(*vpp);
+ }
+ if (nd->nd_repstat) {
+ if (startwrite)
+ vn_finished_write(mp);
+ if (exp->nes_vfslocked) {
+ VFS_UNLOCK_GIANT(mp);
+ exp->nes_vfslocked = 0;
+ }
+ vfs_rel(mp);
+ *vpp = NULL;
+ *mpp = NULL;
+ } else {
+ vfs_rel(mp);
+ }
+}
+
+/*
+ * glue for fp.
+ */
+int
+fp_getfvp(struct thread *p, int fd, struct file **fpp, struct vnode **vpp)
+{
+ struct filedesc *fdp;
+ struct file *fp;
+
+ fdp = p->td_proc->p_fd;
+ if (fd >= fdp->fd_nfiles ||
+ (fp = fdp->fd_ofiles[fd]) == NULL)
+ return (EBADF);
+ *fpp = fp;
+ return (0);
+}
+
+/*
+ * Network export information
+ */
+struct netexport {
+ struct netcred ne_defexported; /* Default export */
+ struct radix_node_head *ne_rtable[AF_MAX+1]; /* Individual exports */
+};
+
+struct netexport nfsv4root_export;
+
+/*
+ * Called from newnfssvc() to update the exports list. Just call
+ * vfs_export(). This has to be done, since the v4 root fake fs isn't
+ * in the mount list.
+ */
+int
+nfsrv_v4rootexport(void *argp, struct ucred *cred, struct thread *p)
+{
+ struct nfsex_args *nfsexargp = (struct nfsex_args *)argp;
+ int error;
+ struct nameidata nd;
+ fhandle_t fh;
+
+ /*
+ * Until newmountd is using the secflavor fields, just make
+ * sure it's 0.
+ */
+ nfsexargp->export.ex_numsecflavors = 0;
+ error = vfs_export(&nfsv4root_mnt, &nfsexargp->export);
+ if ((nfsexargp->export.ex_flags & MNT_DELEXPORT)) {
+ nfs_rootfhset = 0;
+ nfsv4root_set = 0;
+ } else if (error == 0) {
+ if (nfsexargp->fspec == NULL)
+ return (EPERM);
+ /*
+ * If fspec != NULL, this is the v4root path.
+ */
+ NDINIT(&nd, LOOKUP, FOLLOW | MPSAFE, UIO_USERSPACE,
+ nfsexargp->fspec, p);
+ if ((error = namei(&nd)) != 0)
+ return (error);
+ error = nfsvno_getfh(nd.ni_vp, &fh, p);
+ vrele(nd.ni_vp);
+ if (!error) {
+ nfs_rootfh.nfsrvfh_len = NFSX_MYFH;
+ NFSBCOPY((caddr_t)&fh,
+ nfs_rootfh.nfsrvfh_data,
+ sizeof (fhandle_t));
+ nfs_rootfhset = 1;
+ }
+ }
+ return (error);
+}
+
+/*
+ * Get the tcp socket sequence numbers we need.
+ * (Maybe this should be moved to the tcp sources?)
+ */
+int
+nfsrv_getsocksndseq(struct socket *so, tcp_seq *maxp, tcp_seq *unap)
+{
+ struct inpcb *inp;
+ struct tcpcb *tp;
+ int error = EPIPE;
+
+ INP_INFO_RLOCK(&V_tcbinfo);
+ inp = sotoinpcb(so);
+ if (inp == NULL) {
+ INP_INFO_RUNLOCK(&V_tcbinfo);
+ return (error);
+ }
+ INP_RLOCK(inp);
+ INP_INFO_RUNLOCK(&V_tcbinfo);
+ tp = intotcpcb(inp);
+ if (tp != NULL && tp->t_state == TCPS_ESTABLISHED) {
+ *maxp = tp->snd_max;
+ *unap = tp->snd_una;
+ error = 0;
+ }
+ INP_RUNLOCK(inp);
+ return (error);
+}
+
+/*
+ * This function needs to test to see if the system is near its limit
+ * for memory allocation via malloc() or mget() and return True iff
+ * either of these resources are near their limit.
+ * XXX (For now, this is just a stub.)
+ */
+int nfsrv_testmalloclimit = 0;
+int
+nfsrv_mallocmget_limit(void)
+{
+ static int printmesg = 0;
+ static int testval = 1;
+
+ if (nfsrv_testmalloclimit && (testval++ % 1000) == 0) {
+ if ((printmesg++ % 100) == 0)
+ printf("nfsd: malloc/mget near limit\n");
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * BSD specific initialization of a mount point.
+ */
+void
+nfsd_mntinit(void)
+{
+ static int inited = 0;
+
+ if (inited)
+ return;
+ inited = 1;
+ nfsv4root_mnt.mnt_flag = (MNT_RDONLY | MNT_EXPORTED);
+ TAILQ_INIT(&nfsv4root_mnt.mnt_nvnodelist);
+ nfsv4root_mnt.mnt_export = NULL;
+ TAILQ_INIT(&nfsv4root_opt);
+ TAILQ_INIT(&nfsv4root_newopt);
+ nfsv4root_mnt.mnt_opt = &nfsv4root_opt;
+ nfsv4root_mnt.mnt_optnew = &nfsv4root_newopt;
+ nfsv4root_mnt.mnt_nvnodelistsize = 0;
+}
+
+/*
+ * Get a vnode for a file handle, without checking exports, etc.
+ */
+struct vnode *
+nfsvno_getvp(fhandle_t *fhp)
+{
+ struct mount *mp;
+ struct vnode *vp;
+ int error;
+
+ mp = vfs_getvfs(&fhp->fh_fsid);
+ if (mp == NULL)
+ return (NULL);
+ error = VFS_FHTOVP(mp, &fhp->fh_fid, &vp);
+ if (error)
+ return (NULL);
+ return (vp);
+}
+
+static int id_for_advlock;
+/*
+ * Check to see it a byte range lock held by a process running
+ * locally on the server conflicts with the new lock.
+ */
+int
+nfsvno_localconflict(struct vnode *vp, int ftype, u_int64_t first,
+ u_int64_t end, struct nfslockconflict *cfp, struct thread *p)
+{
+ int error;
+ struct flock fl;
+
+ if (!nfsrv_dolocallocks)
+ return (0);
+ fl.l_whence = SEEK_SET;
+ fl.l_type = ftype;
+ fl.l_start = (off_t)first;
+ if (end == NFS64BITSSET)
+ fl.l_len = 0;
+ else
+ fl.l_len = (off_t)(end - first);
+ /*
+ * FreeBSD8 doesn't like 0, so I'll use the address of id_for_advlock.
+ */
+ NFSVOPUNLOCK(vp, 0, p);
+ error = VOP_ADVLOCK(vp, &id_for_advlock, F_GETLK, &fl, F_POSIX);
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ if (error)
+ return (error);
+ if (fl.l_type == F_UNLCK)
+ return (0);
+ if (cfp != NULL) {
+ cfp->cl_clientid.lval[0] = cfp->cl_clientid.lval[1] = 0;
+ cfp->cl_first = (u_int64_t)fl.l_start;
+ if (fl.l_len == 0)
+ cfp->cl_end = NFS64BITSSET;
+ else
+ cfp->cl_end = (u_int64_t)
+ (fl.l_start + fl.l_len);
+ if (fl.l_type == F_WRLCK)
+ cfp->cl_flags = NFSLCK_WRITE;
+ else
+ cfp->cl_flags = NFSLCK_READ;
+ sprintf(cfp->cl_owner, "LOCALID%d", fl.l_pid);
+ cfp->cl_ownerlen = strlen(cfp->cl_owner);
+ return (NFSERR_DENIED);
+ }
+ return (NFSERR_INVAL);
+}
+
+/*
+ * Do a local VOP_ADVLOCK().
+ */
+int
+nfsvno_advlock(struct vnode *vp, int ftype, u_int64_t first,
+ u_int64_t end, struct thread *p)
+{
+ int error;
+ struct flock fl;
+ u_int64_t tlen;
+
+ if (!nfsrv_dolocallocks)
+ return (0);
+ fl.l_whence = SEEK_SET;
+ fl.l_type = ftype;
+ fl.l_start = (off_t)first;
+ if (end == NFS64BITSSET) {
+ fl.l_len = 0;
+ } else {
+ tlen = end - first;
+ fl.l_len = (off_t)tlen;
+ }
+ /*
+ * FreeBSD8 doesn't like 0, so I'll use the address of id_for_advlock.
+ */
+ NFSVOPUNLOCK(vp, 0, p);
+ error = VOP_ADVLOCK(vp, &id_for_advlock, F_SETLK, &fl, F_POSIX);
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ return (error);
+}
+
+/*
+ * Unlock an underlying local file system.
+ */
+void
+nfsvno_unlockvfs(struct mount *mp)
+{
+
+ VFS_UNLOCK_GIANT(mp);
+}
+
+/*
+ * Lock an underlying file system, as required, and return
+ * whether or not it is locked.
+ */
+int
+nfsvno_lockvfs(struct mount *mp)
+{
+ int ret;
+
+ ret = VFS_LOCK_GIANT(mp);
+ return (ret);
+}
+
+/*
+ * Check the nfsv4 root exports.
+ */
+int
+nfsvno_v4rootexport(struct nfsrv_descript *nd)
+{
+ struct ucred *credanon;
+ int exflags, error;
+
+ error = vfs_stdcheckexp(&nfsv4root_mnt, nd->nd_nam, &exflags,
+ &credanon, NULL, NULL);
+ if (error)
+ return (NFSERR_PROGUNAVAIL);
+ if ((exflags & MNT_EXGSSONLY))
+ nd->nd_flag |= ND_EXGSSONLY;
+ return (0);
+}
+
+/*
+ * Nfs server psuedo system call for the nfsd's
+ */
+/*
+ * MPSAFE
+ */
+static int
+nfssvc_nfsd(struct thread *td, struct nfssvc_args *uap)
+{
+ struct file *fp;
+ struct nfsd_args nfsdarg;
+ int error;
+
+ if (uap->flag & NFSSVC_NFSDADDSOCK) {
+ error = copyin(uap->argp, (caddr_t)&nfsdarg, sizeof(nfsdarg));
+ if (error)
+ return (error);
+ if ((error = fget(td, nfsdarg.sock, &fp)) != 0) {
+ return (error);
+ }
+ if (fp->f_type != DTYPE_SOCKET) {
+ fdrop(fp, td);
+ return (EPERM);
+ }
+ error = nfsrvd_addsock(fp);
+ fdrop(fp, td);
+ } else if (uap->flag & NFSSVC_NFSDNFSD) {
+ error = nfsrvd_nfsd(td, NULL);
+ } else {
+ error = nfssvc_srvcall(td, uap, td->td_ucred);
+ }
+ return (error);
+}
+
+static int
+nfssvc_srvcall(struct thread *p, struct nfssvc_args *uap, struct ucred *cred)
+{
+ struct nfsex_args export;
+ struct file *fp = NULL;
+ int stablefd, len;
+ struct nfsd_clid adminrevoke;
+ struct nfsd_dumplist dumplist;
+ struct nfsd_dumpclients *dumpclients;
+ struct nfsd_dumplocklist dumplocklist;
+ struct nfsd_dumplocks *dumplocks;
+ struct nameidata nd;
+ vnode_t vp;
+ int error = EINVAL;
+
+ if (uap->flag & NFSSVC_PUBLICFH) {
+ NFSBZERO((caddr_t)&nfs_pubfh.nfsrvfh_data,
+ sizeof (fhandle_t));
+ error = copyin(uap->argp,
+ &nfs_pubfh.nfsrvfh_data, sizeof (fhandle_t));
+ if (!error)
+ nfs_pubfhset = 1;
+ } else if (uap->flag & NFSSVC_V4ROOTEXPORT) {
+ error = copyin(uap->argp,(caddr_t)&export,
+ sizeof (struct nfsex_args));
+ if (!error)
+ error = nfsrv_v4rootexport(&export, cred, p);
+ } else if (uap->flag & NFSSVC_NOPUBLICFH) {
+ nfs_pubfhset = 0;
+ error = 0;
+ } else if (uap->flag & NFSSVC_STABLERESTART) {
+ error = copyin(uap->argp, (caddr_t)&stablefd,
+ sizeof (int));
+ if (!error)
+ error = fp_getfvp(p, stablefd, &fp, &vp);
+ if (!error && (NFSFPFLAG(fp) & (FREAD | FWRITE)) != (FREAD | FWRITE))
+ error = EBADF;
+ if (!error && newnfs_numnfsd != 0)
+ error = EPERM;
+ if (!error) {
+ nfsrv_stablefirst.nsf_fp = fp;
+ nfsrv_setupstable(p);
+ }
+ } else if (uap->flag & NFSSVC_ADMINREVOKE) {
+ error = copyin(uap->argp, (caddr_t)&adminrevoke,
+ sizeof (struct nfsd_clid));
+ if (!error)
+ error = nfsrv_adminrevoke(&adminrevoke, p);
+ } else if (uap->flag & NFSSVC_DUMPCLIENTS) {
+ error = copyin(uap->argp, (caddr_t)&dumplist,
+ sizeof (struct nfsd_dumplist));
+ if (!error && (dumplist.ndl_size < 1 ||
+ dumplist.ndl_size > NFSRV_MAXDUMPLIST))
+ error = EPERM;
+ if (!error) {
+ len = sizeof (struct nfsd_dumpclients) * dumplist.ndl_size;
+ dumpclients = (struct nfsd_dumpclients *)malloc(len,
+ M_TEMP, M_WAITOK);
+ nfsrv_dumpclients(dumpclients, dumplist.ndl_size);
+ error = copyout(dumpclients,
+ CAST_USER_ADDR_T(dumplist.ndl_list), len);
+ free((caddr_t)dumpclients, M_TEMP);
+ }
+ } else if (uap->flag & NFSSVC_DUMPLOCKS) {
+ error = copyin(uap->argp, (caddr_t)&dumplocklist,
+ sizeof (struct nfsd_dumplocklist));
+ if (!error && (dumplocklist.ndllck_size < 1 ||
+ dumplocklist.ndllck_size > NFSRV_MAXDUMPLIST))
+ error = EPERM;
+ if (!error)
+ error = nfsrv_lookupfilename(&nd,
+ dumplocklist.ndllck_fname, p);
+ if (!error) {
+ len = sizeof (struct nfsd_dumplocks) *
+ dumplocklist.ndllck_size;
+ dumplocks = (struct nfsd_dumplocks *)malloc(len,
+ M_TEMP, M_WAITOK);
+ nfsrv_dumplocks(nd.ni_vp, dumplocks,
+ dumplocklist.ndllck_size, p);
+ vput(nd.ni_vp);
+ error = copyout(dumplocks,
+ CAST_USER_ADDR_T(dumplocklist.ndllck_list), len);
+ free((caddr_t)dumplocks, M_TEMP);
+ }
+ }
+ return (error);
+}
+
+extern int (*nfsd_call_nfsd)(struct thread *, struct nfssvc_args *);
+
+/*
+ * Called once to initialize data structures...
+ */
+static int
+nfsd_modevent(module_t mod, int type, void *data)
+{
+ int error = 0;
+ static int loaded = 0;
+
+ switch (type) {
+ case MOD_LOAD:
+ if (loaded)
+ return (0);
+ newnfs_portinit();
+ mtx_init(&nfs_cache_mutex, "nfs_cache_mutex", NULL, MTX_DEF);
+ mtx_init(&nfs_v4root_mutex, "nfs_v4root_mutex", NULL, MTX_DEF);
+ mtx_init(&nfsv4root_mnt.mnt_mtx, "struct mount mtx", NULL,
+ MTX_DEF);
+ lockinit(&nfsv4root_mnt.mnt_explock, PVFS, "explock", 0, 0);
+ nfsrvd_initcache();
+ nfsd_init();
+ NFSD_LOCK();
+ nfsrvd_init(0);
+ NFSD_UNLOCK();
+ nfsd_mntinit();
+#ifdef VV_DISABLEDELEG
+ vn_deleg_ops.vndeleg_recall = nfsd_recalldelegation;
+ vn_deleg_ops.vndeleg_disable = nfsd_disabledelegation;
+#endif
+ nfsd_call_servertimer = nfsrv_servertimer;
+ nfsd_call_nfsd = nfssvc_nfsd;
+ loaded = 1;
+ break;
+
+ case MOD_UNLOAD:
+ if (newnfs_numnfsd != 0) {
+ error = EBUSY;
+ break;
+ }
+
+#ifdef VV_DISABLEDELEG
+ vn_deleg_ops.vndeleg_recall = NULL;
+ vn_deleg_ops.vndeleg_disable = NULL;
+#endif
+ nfsd_call_servertimer = NULL;
+ nfsd_call_nfsd = NULL;
+ /* and get rid of the locks */
+ mtx_destroy(&nfs_cache_mutex);
+ mtx_destroy(&nfs_v4root_mutex);
+ mtx_destroy(&nfsv4root_mnt.mnt_mtx);
+ lockdestroy(&nfsv4root_mnt.mnt_explock);
+ loaded = 0;
+ break;
+ default:
+ error = EOPNOTSUPP;
+ break;
+ }
+ return error;
+}
+static moduledata_t nfsd_mod = {
+ "nfsd",
+ nfsd_modevent,
+ NULL,
+};
+DECLARE_MODULE(nfsd, nfsd_mod, SI_SUB_VFS, SI_ORDER_ANY);
+
+/* So that loader and kldload(2) can find us, wherever we are.. */
+MODULE_VERSION(nfsd, 1);
+MODULE_DEPEND(nfsd, nfscommon, 1, 1, 1);
+
diff --git a/sys/fs/nfsserver/nfs_nfsdserv.c b/sys/fs/nfsserver/nfs_nfsdserv.c
new file mode 100644
index 0000000..141a614
--- /dev/null
+++ b/sys/fs/nfsserver/nfs_nfsdserv.c
@@ -0,0 +1,3367 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * nfs version 2, 3 and 4 server calls to vnode ops
+ * - these routines generally have 3 phases
+ * 1 - break down and validate rpc request in mbuf list
+ * 2 - do the vnode ops for the request, usually by calling a nfsvno_XXX()
+ * function in nfsd_port.c
+ * 3 - build the rpc reply in an mbuf list
+ * For nfsv4, these functions are called for each Op within the Compound RPC.
+ */
+
+#ifndef APPLEKEXT
+#include <fs/nfs/nfsport.h>
+
+/* Global vars */
+extern u_int32_t newnfs_false, newnfs_true;
+extern enum vtype nv34tov_type[8];
+extern struct timeval nfsboottime;
+extern int nfs_rootfhset, nfsv4root_set;
+#endif /* !APPLEKEXT */
+
+/*
+ * This list defines the GSS mechanisms supported.
+ * (Don't ask me how you get these strings from the RFC stuff like
+ * iso(1), org(3)... but someone did it, so I don't need to know.)
+ */
+static struct nfsgss_mechlist nfsgss_mechlist[] = {
+ { 9, "\052\206\110\206\367\022\001\002\002", 11 },
+ { 0, "", 0 },
+};
+
+/* local functions */
+static void nfsrvd_symlinksub(struct nfsrv_descript *nd, struct nameidata *ndp,
+ struct nfsvattr *nvap, fhandle_t *fhp, vnode_t *vpp,
+ vnode_t dirp, struct nfsvattr *dirforp, struct nfsvattr *diraftp,
+ int *diraft_retp, nfsattrbit_t *attrbitp,
+ NFSACL_T *aclp, NFSPROC_T *p, struct nfsexstuff *exp, char *pathcp,
+ int pathlen);
+static void nfsrvd_mkdirsub(struct nfsrv_descript *nd, struct nameidata *ndp,
+ struct nfsvattr *nvap, fhandle_t *fhp, vnode_t *vpp,
+ vnode_t dirp, struct nfsvattr *dirforp, struct nfsvattr *diraftp,
+ int *diraft_retp, nfsattrbit_t *attrbitp, NFSACL_T *aclp,
+ NFSPROC_T *p, struct nfsexstuff *exp);
+
+/*
+ * nfs access service (not a part of NFS V2)
+ */
+APPLESTATIC int
+nfsrvd_access(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int getret, error = 0;
+ struct nfsvattr nva;
+ u_int32_t testmode, nfsmode, supported = 0;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, 1, &nva);
+ return (0);
+ }
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ nfsmode = fxdr_unsigned(u_int32_t, *tl);
+ if ((nd->nd_flag & ND_NFSV4) &&
+ (nfsmode & ~(NFSACCESS_READ | NFSACCESS_LOOKUP |
+ NFSACCESS_MODIFY | NFSACCESS_EXTEND | NFSACCESS_DELETE |
+ NFSACCESS_EXECUTE))) {
+ nd->nd_repstat = NFSERR_INVAL;
+ vput(vp);
+ return (0);
+ }
+ if (nfsmode & NFSACCESS_READ) {
+ supported |= NFSACCESS_READ;
+ if (nfsvno_accchk(vp, NFSV4ACE_READDATA, nd->nd_cred, exp, p,
+ NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+ nfsmode &= ~NFSACCESS_READ;
+ }
+ if (nfsmode & NFSACCESS_MODIFY) {
+ supported |= NFSACCESS_MODIFY;
+ if (nfsvno_accchk(vp, NFSV4ACE_WRITEDATA, nd->nd_cred, exp, p,
+ NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+ nfsmode &= ~NFSACCESS_MODIFY;
+ }
+ if (nfsmode & NFSACCESS_EXTEND) {
+ supported |= NFSACCESS_EXTEND;
+ if (nfsvno_accchk(vp, NFSV4ACE_APPENDDATA, nd->nd_cred, exp, p,
+ NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+ nfsmode &= ~NFSACCESS_EXTEND;
+ }
+ if (nfsmode & NFSACCESS_DELETE) {
+ supported |= NFSACCESS_DELETE;
+ if (nfsvno_accchk(vp, NFSV4ACE_DELETE, nd->nd_cred, exp, p,
+ NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+ nfsmode &= ~NFSACCESS_DELETE;
+ }
+ if (vnode_vtype(vp) == VDIR)
+ testmode = NFSACCESS_LOOKUP;
+ else
+ testmode = NFSACCESS_EXECUTE;
+ if (nfsmode & testmode) {
+ supported |= (nfsmode & testmode);
+ if (nfsvno_accchk(vp, NFSV4ACE_EXECUTE, nd->nd_cred, exp, p,
+ NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+ nfsmode &= ~testmode;
+ }
+ nfsmode &= supported;
+ if (nd->nd_flag & ND_NFSV3) {
+ getret = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ nfsrv_postopattr(nd, getret, &nva);
+ }
+ vput(vp);
+ if (nd->nd_flag & ND_NFSV4) {
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(supported);
+ } else
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl = txdr_unsigned(nfsmode);
+ return (0);
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfs getattr service
+ */
+APPLESTATIC int
+nfsrvd_getattr(struct nfsrv_descript *nd, int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ struct nfsvattr nva;
+ fhandle_t fh;
+ int error = 0;
+ struct nfsreferral *refp;
+ nfsattrbit_t attrbits;
+
+ if (nd->nd_repstat)
+ return (0);
+ if (nd->nd_flag & ND_NFSV4) {
+ error = nfsrv_getattrbits(nd, &attrbits, NULL, NULL);
+ if (error) {
+ vput(vp);
+ return (error);
+ }
+
+ /*
+ * Check for a referral.
+ */
+ refp = nfsv4root_getreferral(vp, NULL, 0);
+ if (refp != NULL) {
+ (void) nfsrv_putreferralattr(nd, &attrbits, refp, 1,
+ &nd->nd_repstat);
+ vput(vp);
+ return (0);
+ }
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_accchk(vp,
+ NFSV4ACE_READATTRIBUTES,
+ nd->nd_cred, exp, p,
+ NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED);
+ }
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ if (!nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV4) {
+ if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_FILEHANDLE))
+ nd->nd_repstat = nfsvno_getfh(vp, &fh, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsrv_checkgetattr(nd, vp,
+ &nva, &attrbits, nd->nd_cred, p);
+ NFSVOPUNLOCK(vp, 0, p);
+ if (!nd->nd_repstat)
+ (void) nfsvno_fillattr(nd, vp, &nva, &fh,
+ 0, &attrbits, nd->nd_cred, p, isdgram, 1);
+ vrele(vp);
+ } else {
+ nfsrv_fillattr(nd, &nva);
+ vput(vp);
+ }
+ } else {
+ vput(vp);
+ }
+ return (0);
+}
+
+/*
+ * nfs setattr service
+ */
+APPLESTATIC int
+nfsrvd_setattr(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ struct nfsvattr nva, nva2;
+ u_int32_t *tl;
+ int preat_ret = 1, postat_ret = 1, gcheck = 0, error = 0;
+ struct timespec guard = { 0, 0 };
+ nfsattrbit_t attrbits, retbits;
+ nfsv4stateid_t stateid;
+ NFSACL_T *aclp = NULL;
+
+ if (nd->nd_repstat) {
+ nfsrv_wcc(nd, preat_ret, &nva2, postat_ret, &nva);
+ return (0);
+ }
+#ifdef NFS4_ACL_EXTATTR_NAME
+ aclp = acl_alloc();
+ aclp->acl_cnt = 0;
+#endif
+ NFSVNO_ATTRINIT(&nva);
+ NFSZERO_ATTRBIT(&retbits);
+ if (nd->nd_flag & ND_NFSV4) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID);
+ stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ NFSBCOPY((caddr_t)tl,(caddr_t)stateid.other,NFSX_STATEIDOTHER);
+ }
+ error = nfsrv_sattr(nd, &nva, &attrbits, aclp, p);
+ if (error)
+ goto nfsmout;
+ preat_ret = nfsvno_getattr(vp, &nva2, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = preat_ret;
+ if (nd->nd_flag & ND_NFSV3) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ gcheck = fxdr_unsigned(int, *tl);
+ if (gcheck) {
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ fxdr_nfsv3time(tl, &guard);
+ }
+ if (!nd->nd_repstat && gcheck &&
+ (nva2.na_ctime.tv_sec != guard.tv_sec ||
+ nva2.na_ctime.tv_nsec != guard.tv_nsec))
+ nd->nd_repstat = NFSERR_NOT_SYNC;
+ if (nd->nd_repstat) {
+ vput(vp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ nfsrv_wcc(nd, preat_ret, &nva2, postat_ret, &nva);
+ return (0);
+ }
+ } else if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV4))
+ nd->nd_repstat = nfsrv_checkuidgid(nd, &nva);
+
+ /*
+ * Now that we have all the fields, lets do it.
+ * If the size is being changed write access is required, otherwise
+ * just check for a read only file system.
+ */
+ if (!nd->nd_repstat) {
+ if (NFSVNO_NOTSETSIZE(&nva)) {
+ if (NFSVNO_EXRDONLY(exp) ||
+ (vfs_flags(vnode_mount(vp)) & MNT_RDONLY))
+ nd->nd_repstat = EROFS;
+ } else {
+ if (vnode_vtype(vp) != VREG)
+ nd->nd_repstat = EINVAL;
+ else if (nva2.na_uid != nd->nd_cred->cr_uid ||
+ NFSVNO_EXSTRICTACCESS(exp))
+ nd->nd_repstat = nfsvno_accchk(vp,
+ NFSV4ACE_WRITEDATA, nd->nd_cred, exp, p,
+ NFSACCCHK_NOOVERRIDE,NFSACCCHK_VPISLOCKED);
+ }
+ }
+ if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV4))
+ nd->nd_repstat = nfsrv_checksetattr(vp, nd, &stateid,
+ &nva, &attrbits, exp, p);
+
+ if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV4)) {
+ /*
+ * For V4, try setting the attrbutes in sets, so that the
+ * reply bitmap will be correct for an error case.
+ */
+ if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_OWNER) ||
+ NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_OWNERGROUP)) {
+ NFSVNO_ATTRINIT(&nva2);
+ NFSVNO_SETATTRVAL(&nva2, uid, nva.na_uid);
+ NFSVNO_SETATTRVAL(&nva2, gid, nva.na_gid);
+ nd->nd_repstat = nfsvno_setattr(vp, &nva2, nd->nd_cred, p,
+ exp);
+ if (!nd->nd_repstat) {
+ if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_OWNER))
+ NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_OWNER);
+ if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_OWNERGROUP))
+ NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_OWNERGROUP);
+ }
+ }
+ if (!nd->nd_repstat &&
+ NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_SIZE)) {
+ NFSVNO_ATTRINIT(&nva2);
+ NFSVNO_SETATTRVAL(&nva2, size, nva.na_size);
+ nd->nd_repstat = nfsvno_setattr(vp, &nva2, nd->nd_cred, p,
+ exp);
+ if (!nd->nd_repstat)
+ NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_SIZE);
+ }
+ if (!nd->nd_repstat &&
+ (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_TIMEACCESSSET) ||
+ NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_TIMEMODIFYSET))) {
+ NFSVNO_ATTRINIT(&nva2);
+ NFSVNO_SETATTRVAL(&nva2, atime, nva.na_atime);
+ NFSVNO_SETATTRVAL(&nva2, mtime, nva.na_mtime);
+ if (nva.na_vaflags & VA_UTIMES_NULL) {
+ nva2.na_vaflags |= VA_UTIMES_NULL;
+ NFSVNO_SETACTIVE(&nva2, vaflags);
+ }
+ nd->nd_repstat = nfsvno_setattr(vp, &nva2, nd->nd_cred, p,
+ exp);
+ if (!nd->nd_repstat) {
+ if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_TIMEACCESSSET))
+ NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_TIMEACCESSSET);
+ if (NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_TIMEMODIFYSET))
+ NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_TIMEMODIFYSET);
+ }
+ }
+ if (!nd->nd_repstat &&
+ NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_MODE)) {
+ NFSVNO_ATTRINIT(&nva2);
+ NFSVNO_SETATTRVAL(&nva2, mode, nva.na_mode);
+ nd->nd_repstat = nfsvno_setattr(vp, &nva2, nd->nd_cred, p,
+ exp);
+ if (!nd->nd_repstat)
+ NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_MODE);
+ }
+
+#ifdef NFS4_ACL_EXTATTR_NAME
+ if (!nd->nd_repstat && aclp->acl_cnt > 0 &&
+ NFSISSET_ATTRBIT(&attrbits, NFSATTRBIT_ACL)) {
+ nd->nd_repstat = nfsrv_setacl(vp, aclp, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ NFSSETBIT_ATTRBIT(&retbits, NFSATTRBIT_ACL);
+ }
+#endif
+ } else if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsvno_setattr(vp, &nva, nd->nd_cred, p,
+ exp);
+ }
+ if (nd->nd_flag & (ND_NFSV2 | ND_NFSV3)) {
+ postat_ret = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = postat_ret;
+ }
+ vput(vp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_wcc(nd, preat_ret, &nva2, postat_ret, &nva);
+ else if (nd->nd_flag & ND_NFSV4)
+ (void) nfsrv_putattrbit(nd, &retbits);
+ else if (!nd->nd_repstat)
+ nfsrv_fillattr(nd, &nva);
+ return (0);
+nfsmout:
+ vput(vp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ if (nd->nd_flag & ND_NFSV4) {
+ /*
+ * For all nd_repstat, the V4 reply includes a bitmap,
+ * even NFSERR_BADXDR, which is what this will end up
+ * returning.
+ */
+ (void) nfsrv_putattrbit(nd, &retbits);
+ }
+ return (error);
+}
+
+/*
+ * nfs lookup rpc
+ * (Also performs lookup parent for v4)
+ */
+APPLESTATIC int
+nfsrvd_lookup(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t dp, vnode_t *vpp, fhandle_t *fhp, NFSPROC_T *p,
+ __unused struct nfsexstuff *exp)
+{
+ struct nameidata named;
+ vnode_t vp, dirp = NULL;
+ int error, dattr_ret = 1;
+ struct nfsvattr nva, dattr;
+ char *bufp;
+ u_long *hashp;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, dattr_ret, &dattr);
+ return (0);
+ }
+
+ /*
+ * For some reason, if dp is a symlink, the error
+ * returned is supposed to be NFSERR_SYMLINK and not NFSERR_NOTDIR.
+ */
+ if (dp->v_type == VLNK && (nd->nd_flag & ND_NFSV4)) {
+ nd->nd_repstat = NFSERR_SYMLINK;
+ vrele(dp);
+ return (0);
+ }
+
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, LOOKUP,
+ LOCKLEAF | SAVESTART);
+ nfsvno_setpathbuf(&named, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
+ if (error) {
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp, p, &dirp);
+ } else {
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ }
+ if (nd->nd_repstat) {
+ if (dirp) {
+ if (nd->nd_flag & ND_NFSV3)
+ dattr_ret = nfsvno_getattr(dirp, &dattr,
+ nd->nd_cred, p);
+ vrele(dirp);
+ }
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, dattr_ret, &dattr);
+ return (0);
+ }
+ if (named.ni_startdir)
+ vrele(named.ni_startdir);
+ nfsvno_relpathbuf(&named);
+ vp = named.ni_vp;
+ nd->nd_repstat = nfsvno_getfh(vp, fhp, p);
+ if (!(nd->nd_flag & ND_NFSV4) && !nd->nd_repstat)
+ nd->nd_repstat = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ if (vpp) {
+ NFSVOPUNLOCK(vp, 0, p);
+ *vpp = vp;
+ } else {
+ vput(vp);
+ }
+ if (dirp) {
+ if (nd->nd_flag & ND_NFSV3)
+ dattr_ret = nfsvno_getattr(dirp, &dattr, nd->nd_cred,
+ p);
+ vrele(dirp);
+ }
+ if (nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, dattr_ret, &dattr);
+ return (0);
+ }
+ if (nd->nd_flag & ND_NFSV2) {
+ (void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 0);
+ nfsrv_fillattr(nd, &nva);
+ } else if (nd->nd_flag & ND_NFSV3) {
+ (void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 0);
+ nfsrv_postopattr(nd, 0, &nva);
+ nfsrv_postopattr(nd, dattr_ret, &dattr);
+ }
+ return (0);
+}
+
+/*
+ * nfs readlink service
+ */
+APPLESTATIC int
+nfsrvd_readlink(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ mbuf_t mp = NULL, mpend = NULL;
+ int getret = 1, len;
+ struct nfsvattr nva;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, getret, &nva);
+ return (0);
+ }
+ if (vnode_vtype(vp) != VLNK) {
+ if (nd->nd_flag & ND_NFSV2)
+ nd->nd_repstat = ENXIO;
+ else
+ nd->nd_repstat = EINVAL;
+ }
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_readlink(vp, nd->nd_cred, p,
+ &mp, &mpend, &len);
+ if (nd->nd_flag & ND_NFSV3)
+ getret = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ vput(vp);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, getret, &nva);
+ if (nd->nd_repstat)
+ return (0);
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl = txdr_unsigned(len);
+ mbuf_setnext(nd->nd_mb, mp);
+ nd->nd_mb = mpend;
+ nd->nd_bpos = NFSMTOD(mpend, caddr_t) + mbuf_len(mpend);
+ return (0);
+}
+
+/*
+ * nfs read service
+ */
+APPLESTATIC int
+nfsrvd_read(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int error = 0, cnt, len, getret = 1, reqlen, eof = 0;
+ mbuf_t m2, m3;
+ struct nfsvattr nva;
+ off_t off = 0x0;
+ struct nfsstate st, *stp = &st;
+ struct nfslock lo, *lop = &lo;
+ nfsv4stateid_t stateid;
+ nfsquad_t clientid;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, getret, &nva);
+ return (0);
+ }
+ if (nd->nd_flag & ND_NFSV2) {
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ off = (off_t)fxdr_unsigned(u_int32_t, *tl++);
+ reqlen = fxdr_unsigned(int, *tl);
+ } else if (nd->nd_flag & ND_NFSV3) {
+ NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
+ off = fxdr_hyper(tl);
+ tl += 2;
+ reqlen = fxdr_unsigned(int, *tl);
+ } else {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + 3*NFSX_UNSIGNED);
+ reqlen = fxdr_unsigned(int, *(tl + 6));
+ }
+ if (reqlen > NFS_SRVMAXDATA(nd)) {
+ reqlen = NFS_SRVMAXDATA(nd);
+ } else if (reqlen < 0) {
+ error = EBADRPC;
+ goto nfsmout;
+ }
+ if (nd->nd_flag & ND_NFSV4) {
+ stp->ls_flags = (NFSLCK_CHECK | NFSLCK_READACCESS);
+ lop->lo_flags = NFSLCK_READ;
+ stp->ls_ownerlen = 0;
+ stp->ls_op = NULL;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ clientid.lval[0] = stp->ls_stateid.other[0] = *tl++;
+ clientid.lval[1] = stp->ls_stateid.other[1] = *tl++;
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ stp->ls_stateid.other[2] = *tl++;
+ off = fxdr_hyper(tl);
+ lop->lo_first = off;
+ tl += 2;
+ lop->lo_end = off + reqlen;
+ /*
+ * Paranoia, just in case it wraps around.
+ */
+ if (lop->lo_end < off)
+ lop->lo_end = NFS64BITSSET;
+ }
+ if (vnode_vtype(vp) != VREG) {
+ if (nd->nd_flag & ND_NFSV3)
+ nd->nd_repstat = EINVAL;
+ else
+ nd->nd_repstat = (vnode_vtype(vp) == VDIR) ? EISDIR :
+ EINVAL;
+ }
+ getret = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = getret;
+ if (!nd->nd_repstat &&
+ (nva.na_uid != nd->nd_cred->cr_uid ||
+ NFSVNO_EXSTRICTACCESS(exp))) {
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_READDATA,
+ nd->nd_cred, exp, p,
+ NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+ if (nd->nd_repstat)
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_EXECUTE,
+ nd->nd_cred, exp, p,
+ NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+ }
+ if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat)
+ nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
+ &stateid, exp, nd, p);
+ if (nd->nd_repstat) {
+ vput(vp);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, getret, &nva);
+ return (0);
+ }
+ if (off >= nva.na_size) {
+ cnt = 0;
+ eof = 1;
+ } else if (reqlen == 0)
+ cnt = 0;
+ else if ((off + reqlen) > nva.na_size)
+ cnt = nva.na_size - off;
+ else
+ cnt = reqlen;
+ len = NFSM_RNDUP(cnt);
+ m3 = NULL;
+ if (cnt > 0) {
+ nd->nd_repstat = nfsvno_read(vp, off, cnt, nd->nd_cred, p,
+ &m3, &m2);
+ if (!(nd->nd_flag & ND_NFSV4)) {
+ getret = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = getret;
+ }
+ if (nd->nd_repstat) {
+ vput(vp);
+ if (m3)
+ mbuf_freem(m3);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, getret, &nva);
+ return (0);
+ }
+ }
+ vput(vp);
+ if (nd->nd_flag & ND_NFSV2) {
+ nfsrv_fillattr(nd, &nva);
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ } else {
+ if (nd->nd_flag & ND_NFSV3) {
+ nfsrv_postopattr(nd, getret, &nva);
+ NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(cnt);
+ } else
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ if (len < reqlen || eof)
+ *tl++ = newnfs_true;
+ else
+ *tl++ = newnfs_false;
+ }
+ *tl = txdr_unsigned(cnt);
+ if (m3) {
+ mbuf_setnext(nd->nd_mb, m3);
+ nd->nd_mb = m2;
+ nd->nd_bpos = NFSMTOD(m2, caddr_t) + mbuf_len(m2);
+ }
+ return (0);
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfs write service
+ */
+APPLESTATIC int
+nfsrvd_write(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ int i, cnt;
+ u_int32_t *tl;
+ mbuf_t mp;
+ struct nfsvattr nva, forat;
+ int aftat_ret = 1, retlen, len, error = 0, forat_ret = 1;
+ int stable = NFSWRITE_FILESYNC;
+ off_t off;
+ struct nfsstate st, *stp = &st;
+ struct nfslock lo, *lop = &lo;
+ nfsv4stateid_t stateid;
+ nfsquad_t clientid;
+
+ if (nd->nd_repstat) {
+ nfsrv_wcc(nd, forat_ret, &forat, aftat_ret, &nva);
+ return (0);
+ }
+ if (nd->nd_flag & ND_NFSV2) {
+ NFSM_DISSECT(tl, u_int32_t *, 4 * NFSX_UNSIGNED);
+ off = (off_t)fxdr_unsigned(u_int32_t, *++tl);
+ tl += 2;
+ retlen = len = fxdr_unsigned(int32_t, *tl);
+ } else if (nd->nd_flag & ND_NFSV3) {
+ NFSM_DISSECT(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
+ off = fxdr_hyper(tl);
+ tl += 3;
+ stable = fxdr_unsigned(int, *tl++);
+ retlen = len = fxdr_unsigned(int32_t, *tl);
+ } else {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + 4 * NFSX_UNSIGNED);
+ stp->ls_flags = (NFSLCK_CHECK | NFSLCK_WRITEACCESS);
+ lop->lo_flags = NFSLCK_WRITE;
+ stp->ls_ownerlen = 0;
+ stp->ls_op = NULL;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ clientid.lval[0] = stp->ls_stateid.other[0] = *tl++;
+ clientid.lval[1] = stp->ls_stateid.other[1] = *tl++;
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ stp->ls_stateid.other[2] = *tl++;
+ off = fxdr_hyper(tl);
+ lop->lo_first = off;
+ tl += 2;
+ stable = fxdr_unsigned(int, *tl++);
+ retlen = len = fxdr_unsigned(int32_t, *tl);
+ lop->lo_end = off + len;
+ /*
+ * Paranoia, just in case it wraps around, which shouldn't
+ * ever happen anyhow.
+ */
+ if (lop->lo_end < lop->lo_first)
+ lop->lo_end = NFS64BITSSET;
+ }
+
+ /*
+ * Loop through the mbuf chain, counting how many mbufs are a
+ * part of this write operation, so the iovec size is known.
+ */
+ cnt = 0;
+ mp = nd->nd_md;
+ i = NFSMTOD(mp, caddr_t) + mbuf_len(mp) - nd->nd_dpos;
+ while (len > 0) {
+ if (i > 0) {
+ len -= i;
+ cnt++;
+ }
+ mp = mbuf_next(mp);
+ if (!mp) {
+ if (len > 0) {
+ error = EBADRPC;
+ goto nfsmout;
+ }
+ } else
+ i = mbuf_len(mp);
+ }
+
+ if (retlen > NFS_MAXDATA || retlen < 0)
+ nd->nd_repstat = EIO;
+ if (vnode_vtype(vp) != VREG && !nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV3)
+ nd->nd_repstat = EINVAL;
+ else
+ nd->nd_repstat = (vnode_vtype(vp) == VDIR) ? EISDIR :
+ EINVAL;
+ }
+ forat_ret = nfsvno_getattr(vp, &forat, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = forat_ret;
+ if (!nd->nd_repstat &&
+ (forat.na_uid != nd->nd_cred->cr_uid ||
+ NFSVNO_EXSTRICTACCESS(exp)))
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_WRITEDATA,
+ nd->nd_cred, exp, p,
+ NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+ if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
+ nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
+ &stateid, exp, nd, p);
+ }
+ if (nd->nd_repstat) {
+ vput(vp);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_wcc(nd, forat_ret, &forat, aftat_ret, &nva);
+ return (0);
+ }
+
+ /*
+ * For NFS Version 2, it is not obvious what a write of zero length
+ * should do, but I might as well be consistent with Version 3,
+ * which is to return ok so long as there are no permission problems.
+ */
+ if (retlen > 0) {
+ nd->nd_repstat = nfsvno_write(vp, off, retlen, cnt, stable,
+ nd->nd_md, nd->nd_dpos, nd->nd_cred, p);
+ error = nfsm_advance(nd, NFSM_RNDUP(retlen), -1);
+ if (error)
+ panic("nfsrv_write mbuf");
+ }
+ if (nd->nd_flag & ND_NFSV4)
+ aftat_ret = 0;
+ else
+ aftat_ret = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ vput(vp);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = aftat_ret;
+ if (nd->nd_flag & (ND_NFSV3 | ND_NFSV4)) {
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_wcc(nd, forat_ret, &forat, aftat_ret, &nva);
+ if (nd->nd_repstat)
+ return (0);
+ NFSM_BUILD(tl, u_int32_t *, 4 * NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(retlen);
+ if (stable == NFSWRITE_UNSTABLE)
+ *tl++ = txdr_unsigned(stable);
+ else
+ *tl++ = txdr_unsigned(NFSWRITE_FILESYNC);
+ /*
+ * Actually, there is no need to txdr these fields,
+ * but it may make the values more human readable,
+ * for debugging purposes.
+ */
+ *tl++ = txdr_unsigned(nfsboottime.tv_sec);
+ *tl = txdr_unsigned(nfsboottime.tv_usec);
+ } else if (!nd->nd_repstat)
+ nfsrv_fillattr(nd, &nva);
+ return (0);
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfs create service (creates regular files for V2 and V3. Spec. files for V2.)
+ * now does a truncate to 0 length via. setattr if it already exists
+ * The core creation routine has been extracted out into nfsrv_creatsub(),
+ * so it can also be used by nfsrv_open() for V4.
+ */
+APPLESTATIC int
+nfsrvd_create(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t dp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ struct nfsvattr nva, dirfor, diraft;
+ struct nfsv2_sattr *sp;
+ struct nameidata named;
+ u_int32_t *tl;
+ int error = 0, tsize, dirfor_ret = 1, diraft_ret = 1;
+ int how = NFSCREATE_UNCHECKED, exclusive_flag = 0;
+ NFSDEV_T rdev = 0;
+ vnode_t vp = NULL, dirp = NULL;
+ u_char cverf[NFSX_VERF], *cp;
+ fhandle_t fh;
+ char *bufp;
+ u_long *hashp;
+ enum vtype vtyp;
+
+ if (nd->nd_repstat) {
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ return (0);
+ }
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE,
+ LOCKPARENT | LOCKLEAF | SAVESTART);
+ nfsvno_setpathbuf(&named, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
+ if (error) {
+ vput(dp);
+ nfsvno_relpathbuf(&named);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ NFSVNO_ATTRINIT(&nva);
+ if (nd->nd_flag & ND_NFSV2) {
+ NFSM_DISSECT(sp, struct nfsv2_sattr *, NFSX_V2SATTR);
+ vtyp = IFTOVT(fxdr_unsigned(u_int32_t, sp->sa_mode));
+ if (vtyp == VNON)
+ vtyp = VREG;
+ NFSVNO_SETATTRVAL(&nva, type, vtyp);
+ NFSVNO_SETATTRVAL(&nva, mode,
+ nfstov_mode(sp->sa_mode));
+ switch (nva.na_type) {
+ case VREG:
+ tsize = fxdr_unsigned(int32_t, sp->sa_size);
+ if (tsize != -1)
+ NFSVNO_SETATTRVAL(&nva, size,
+ (u_quad_t)tsize);
+ break;
+ case VCHR:
+ case VBLK:
+ case VFIFO:
+ rdev = fxdr_unsigned(NFSDEV_T, sp->sa_size);
+ break;
+ default:
+ break;
+ };
+ } else {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ how = fxdr_unsigned(int, *tl);
+ switch (how) {
+ case NFSCREATE_GUARDED:
+ case NFSCREATE_UNCHECKED:
+ error = nfsrv_sattr(nd, &nva, NULL, NULL, p);
+ if (error)
+ goto nfsmout;
+ break;
+ case NFSCREATE_EXCLUSIVE:
+ NFSM_DISSECT(cp, u_char *, NFSX_VERF);
+ NFSBCOPY(cp, cverf, NFSX_VERF);
+ exclusive_flag = 1;
+ break;
+ };
+ NFSVNO_SETATTRVAL(&nva, type, VREG);
+ }
+ }
+ if (nd->nd_repstat) {
+ nfsvno_relpathbuf(&named);
+ if (nd->nd_flag & ND_NFSV3) {
+ dirfor_ret = nfsvno_getattr(dp, &dirfor, nd->nd_cred,
+ p);
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
+ &diraft);
+ }
+ vput(dp);
+ return (0);
+ }
+
+ nd->nd_repstat = nfsvno_namei(nd, &named, dp, 1, exp, p, &dirp);
+ if (dirp) {
+ if (nd->nd_flag & ND_NFSV2) {
+ vrele(dirp);
+ dirp = NULL;
+ } else {
+ dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd->nd_cred,
+ p);
+ }
+ }
+ if (nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
+ &diraft);
+ if (dirp)
+ vrele(dirp);
+ return (0);
+ }
+
+ if (!(nd->nd_flag & ND_NFSV2)) {
+ switch (how) {
+ case NFSCREATE_GUARDED:
+ if (named.ni_vp)
+ nd->nd_repstat = EEXIST;
+ break;
+ case NFSCREATE_UNCHECKED:
+ break;
+ case NFSCREATE_EXCLUSIVE:
+ if (named.ni_vp == NULL)
+ NFSVNO_SETATTRVAL(&nva, mode, 0);
+ break;
+ };
+ }
+
+ /*
+ * Iff doesn't exist, create it
+ * otherwise just truncate to 0 length
+ * should I set the mode too ?
+ */
+ nd->nd_repstat = nfsvno_createsub(nd, &named, &vp, &nva,
+ &exclusive_flag, cverf, rdev, p, exp);
+
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsvno_getfh(vp, &fh, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_getattr(vp, &nva, nd->nd_cred,
+ p);
+ vput(vp);
+ }
+ if (nd->nd_flag & ND_NFSV2) {
+ if (!nd->nd_repstat) {
+ (void) nfsm_fhtom(nd, (u_int8_t *)&fh, 0, 0);
+ nfsrv_fillattr(nd, &nva);
+ }
+ } else {
+ if (exclusive_flag && !nd->nd_repstat &&
+ NFSBCMP(cverf, (caddr_t)&nva.na_atime, NFSX_VERF))
+ nd->nd_repstat = EEXIST;
+ diraft_ret = nfsvno_getattr(dirp, &diraft, nd->nd_cred, p);
+ vrele(dirp);
+ if (!nd->nd_repstat) {
+ (void) nfsm_fhtom(nd, (u_int8_t *)&fh, 0, 1);
+ nfsrv_postopattr(nd, 0, &nva);
+ }
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ }
+ return (0);
+nfsmout:
+ vput(dp);
+ nfsvno_relpathbuf(&named);
+ return (error);
+}
+
+/*
+ * nfs v3 mknod service (and v4 create)
+ */
+APPLESTATIC int
+nfsrvd_mknod(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t dp, vnode_t *vpp, fhandle_t *fhp, NFSPROC_T *p,
+ struct nfsexstuff *exp)
+{
+ struct nfsvattr nva, dirfor, diraft;
+ u_int32_t *tl;
+ struct nameidata named;
+ int error = 0, dirfor_ret = 1, diraft_ret = 1, pathlen;
+ u_int32_t major, minor;
+ enum vtype vtyp = VNON;
+ nfstype nfs4type = NFNON;
+ vnode_t vp, dirp = NULL;
+ nfsattrbit_t attrbits;
+ char *bufp = NULL, *pathcp = NULL;
+ u_long *hashp, cnflags;
+ NFSACL_T *aclp = NULL;
+
+ NFSVNO_ATTRINIT(&nva);
+ cnflags = (LOCKPARENT | SAVESTART);
+ if (nd->nd_repstat) {
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ return (0);
+ }
+#ifdef NFS4_ACL_EXTATTR_NAME
+ aclp = acl_alloc();
+ aclp->acl_cnt = 0;
+#endif
+
+ /*
+ * For V4, the creation stuff is here, Yuck!
+ */
+ if (nd->nd_flag & ND_NFSV4) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ vtyp = nfsv34tov_type(*tl);
+ nfs4type = fxdr_unsigned(nfstype, *tl);
+ switch (nfs4type) {
+ case NFLNK:
+ error = nfsvno_getsymlink(nd, &nva, p, &pathcp,
+ &pathlen);
+ if (error) {
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ return (error);
+ }
+ break;
+ case NFCHR:
+ case NFBLK:
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ major = fxdr_unsigned(u_int32_t, *tl++);
+ minor = fxdr_unsigned(u_int32_t, *tl);
+ nva.na_rdev = NFSMAKEDEV(major, minor);
+ break;
+ case NFSOCK:
+ case NFFIFO:
+ break;
+ case NFDIR:
+ cnflags = LOCKPARENT;
+ break;
+ default:
+ nd->nd_repstat = NFSERR_BADTYPE;
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ return (0);
+ };
+ }
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE, cnflags);
+ nfsvno_setpathbuf(&named, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
+ if (error) {
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ nfsvno_relpathbuf(&named);
+ if (pathcp)
+ FREE(pathcp, M_TEMP);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV3) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ vtyp = nfsv34tov_type(*tl);
+ }
+ error = nfsrv_sattr(nd, &nva, &attrbits, aclp, p);
+ if (error) {
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ nfsvno_relpathbuf(&named);
+ if (pathcp)
+ FREE(pathcp, M_TEMP);
+ return (error);
+ }
+ nva.na_type = vtyp;
+ if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV3) &&
+ (vtyp == VCHR || vtyp == VBLK)) {
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ major = fxdr_unsigned(u_int32_t, *tl++);
+ minor = fxdr_unsigned(u_int32_t, *tl);
+ nva.na_rdev = NFSMAKEDEV(major, minor);
+ }
+ }
+
+ dirfor_ret = nfsvno_getattr(dp, &dirfor, nd->nd_cred, p);
+ if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV4)) {
+ if (!dirfor_ret && NFSVNO_ISSETGID(&nva) &&
+ dirfor.na_gid == nva.na_gid)
+ NFSVNO_UNSET(&nva, gid);
+ nd->nd_repstat = nfsrv_checkuidgid(nd, &nva);
+ }
+ if (nd->nd_repstat) {
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ nfsvno_relpathbuf(&named);
+ if (pathcp)
+ FREE(pathcp, M_TEMP);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
+ &diraft);
+ return (0);
+ }
+
+ /*
+ * Yuck! For V4, mkdir and link are here and some V4 clients don't fill
+ * in va_mode, so we'll have to set a default here.
+ */
+ if (NFSVNO_NOTSETMODE(&nva)) {
+ if (vtyp == VLNK)
+ nva.na_mode = 0755;
+ else
+ nva.na_mode = 0400;
+ }
+
+ if (vtyp == VDIR)
+ named.ni_cnd.cn_flags |= WILLBEDIR;
+ nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp, p, &dirp);
+ if (nd->nd_repstat) {
+ if (dirp) {
+ if (nd->nd_flag & ND_NFSV3)
+ dirfor_ret = nfsvno_getattr(dirp, &dirfor,
+ nd->nd_cred, p);
+ vrele(dirp);
+ }
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
+ &diraft);
+ return (0);
+ }
+ if (dirp)
+ dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd->nd_cred, p);
+
+ if ((nd->nd_flag & ND_NFSV4) && (vtyp == VDIR || vtyp == VLNK)) {
+ if (vtyp == VDIR) {
+ nfsrvd_mkdirsub(nd, &named, &nva, fhp, vpp, dirp,
+ &dirfor, &diraft, &diraft_ret, &attrbits, aclp, p,
+ exp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ return (0);
+ } else if (vtyp == VLNK) {
+ nfsrvd_symlinksub(nd, &named, &nva, fhp, vpp, dirp,
+ &dirfor, &diraft, &diraft_ret, &attrbits,
+ aclp, p, exp, pathcp, pathlen);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ FREE(pathcp, M_TEMP);
+ return (0);
+ }
+ }
+
+ nd->nd_repstat = nfsvno_mknod(&named, &nva, nd->nd_cred, p);
+ if (!nd->nd_repstat) {
+ vp = named.ni_vp;
+ nfsrv_fixattr(nd, vp, &nva, aclp, p, &attrbits, exp);
+ nd->nd_repstat = nfsvno_getfh(vp, fhp, p);
+ if ((nd->nd_flag & ND_NFSV3) && !nd->nd_repstat)
+ nd->nd_repstat = nfsvno_getattr(vp, &nva, nd->nd_cred,
+ p);
+ if (vpp) {
+ NFSVOPUNLOCK(vp, 0, p);
+ *vpp = vp;
+ } else {
+ vput(vp);
+ }
+ }
+
+ diraft_ret = nfsvno_getattr(dirp, &diraft, nd->nd_cred, p);
+ vrele(dirp);
+ if (!nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV3) {
+ (void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 1);
+ nfsrv_postopattr(nd, 0, &nva);
+ } else {
+ NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
+ *tl++ = newnfs_false;
+ txdr_hyper(dirfor.na_filerev, tl);
+ tl += 2;
+ txdr_hyper(diraft.na_filerev, tl);
+ (void) nfsrv_putattrbit(nd, &attrbits);
+ }
+ }
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ return (0);
+nfsmout:
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ if (bufp)
+ nfsvno_relpathbuf(&named);
+ if (pathcp)
+ FREE(pathcp, M_TEMP);
+ return (error);
+}
+
+/*
+ * nfs remove service
+ */
+APPLESTATIC int
+nfsrvd_remove(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t dp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ struct nameidata named;
+ u_int32_t *tl;
+ int error, dirfor_ret = 1, diraft_ret = 1;
+ vnode_t dirp = NULL;
+ struct nfsvattr dirfor, diraft;
+ char *bufp;
+ u_long *hashp;
+
+ if (nd->nd_repstat) {
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ return (0);
+ }
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, DELETE,
+ LOCKPARENT | LOCKLEAF);
+ nfsvno_setpathbuf(&named, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
+ if (error) {
+ vput(dp);
+ nfsvno_relpathbuf(&named);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsvno_namei(nd, &named, dp, 1, exp, p, &dirp);
+ } else {
+ vput(dp);
+ nfsvno_relpathbuf(&named);
+ }
+ if (dirp) {
+ if (!(nd->nd_flag & ND_NFSV2)) {
+ dirfor_ret = nfsvno_getattr(dirp, &dirfor,
+ nd->nd_cred, p);
+ } else {
+ vrele(dirp);
+ dirp = NULL;
+ }
+ }
+ if (!nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV4) {
+ if (vnode_vtype(named.ni_vp) == VDIR)
+ nd->nd_repstat = nfsvno_rmdirsub(&named, 1,
+ nd->nd_cred, p, exp);
+ else
+ nd->nd_repstat = nfsvno_removesub(&named, 1,
+ nd->nd_cred, p, exp);
+ } else if (nd->nd_procnum == NFSPROC_RMDIR) {
+ nd->nd_repstat = nfsvno_rmdirsub(&named, 0,
+ nd->nd_cred, p, exp);
+ } else {
+ nd->nd_repstat = nfsvno_removesub(&named, 0,
+ nd->nd_cred, p, exp);
+ }
+ }
+ if (!(nd->nd_flag & ND_NFSV2)) {
+ if (dirp) {
+ diraft_ret = nfsvno_getattr(dirp, &diraft, nd->nd_cred,
+ p);
+ vrele(dirp);
+ }
+ if (nd->nd_flag & ND_NFSV3) {
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
+ &diraft);
+ } else if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
+ *tl++ = newnfs_false;
+ txdr_hyper(dirfor.na_filerev, tl);
+ tl += 2;
+ txdr_hyper(diraft.na_filerev, tl);
+ }
+ }
+ return (0);
+}
+
+/*
+ * nfs rename service
+ */
+APPLESTATIC int
+nfsrvd_rename(struct nfsrv_descript *nd, int isdgram,
+ vnode_t dp, vnode_t todp, NFSPROC_T *p, struct nfsexstuff *exp,
+ struct nfsexstuff *toexp)
+{
+ u_int32_t *tl;
+ int error, fdirfor_ret = 1, fdiraft_ret = 1;
+ int tdirfor_ret = 1, tdiraft_ret = 1;
+ struct nameidata fromnd, tond;
+ vnode_t fdirp = NULL, tdirp = NULL, tdp = NULL;
+ struct nfsvattr fdirfor, fdiraft, tdirfor, tdiraft;
+ struct nfsexstuff tnes;
+ struct nfsrvfh tfh;
+ mount_t mp = NULL;
+ char *bufp, *tbufp = NULL;
+ u_long *hashp;
+
+ if (nd->nd_repstat) {
+ nfsrv_wcc(nd, fdirfor_ret, &fdirfor, fdiraft_ret, &fdiraft);
+ nfsrv_wcc(nd, tdirfor_ret, &tdirfor, tdiraft_ret, &tdiraft);
+ return (0);
+ }
+ if (!(nd->nd_flag & ND_NFSV2))
+ fdirfor_ret = nfsvno_getattr(dp, &fdirfor, nd->nd_cred, p);
+ tond.ni_cnd.cn_nameiop = 0;
+ tond.ni_startdir = NULL;
+ NFSNAMEICNDSET(&fromnd.ni_cnd, nd->nd_cred, DELETE, WANTPARENT | SAVESTART);
+ nfsvno_setpathbuf(&fromnd, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &fromnd.ni_pathlen);
+ if (error) {
+ vput(dp);
+ if (todp)
+ vrele(todp);
+ nfsvno_relpathbuf(&fromnd);
+ return (error);
+ }
+ if (nd->nd_flag & ND_NFSV4) {
+ tdp = todp;
+ tnes = *toexp;
+ tdirfor_ret = nfsvno_getattr(tdp, &tdirfor, nd->nd_cred, p);
+ } else {
+ error = nfsrv_mtofh(nd, &tfh);
+ if (error) {
+ vput(dp);
+ /* todp is always NULL except NFSv4 */
+ nfsvno_relpathbuf(&fromnd);
+ return (error);
+ }
+ nd->nd_cred->cr_uid = nd->nd_saveduid;
+ /* Won't lock vfs if already locked, mp == NULL */
+ tnes.nes_vfslocked = exp->nes_vfslocked;
+ nfsd_fhtovp(nd, &tfh, &tdp, &tnes, &mp, 0, p);
+ if (tdp) {
+ tdirfor_ret = nfsvno_getattr(tdp, &tdirfor, nd->nd_cred,
+ p);
+ NFSVOPUNLOCK(tdp, 0, p);
+ }
+ }
+ NFSNAMEICNDSET(&tond.ni_cnd, nd->nd_cred, RENAME, LOCKPARENT | LOCKLEAF | NOCACHE | SAVESTART);
+ nfsvno_setpathbuf(&tond, &tbufp, &hashp);
+ if (!nd->nd_repstat) {
+ error = nfsrv_parsename(nd, tbufp, hashp, &tond.ni_pathlen);
+ if (error) {
+ if (tdp) {
+ if (tnes.nes_vfslocked && !exp->nes_vfslocked &&
+ !(nd->nd_flag & ND_NFSV4))
+ nfsvno_unlockvfs(mp);
+ vrele(tdp);
+ }
+ vput(dp);
+ nfsvno_relpathbuf(&fromnd);
+ nfsvno_relpathbuf(&tond);
+ return (error);
+ }
+ }
+ if (nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV3) {
+ nfsrv_wcc(nd, fdirfor_ret, &fdirfor, fdiraft_ret,
+ &fdiraft);
+ nfsrv_wcc(nd, tdirfor_ret, &tdirfor, tdiraft_ret,
+ &tdiraft);
+ }
+ if (tdp) {
+ if (tnes.nes_vfslocked && !exp->nes_vfslocked &&
+ !(nd->nd_flag & ND_NFSV4))
+ nfsvno_unlockvfs(mp);
+ vrele(tdp);
+ }
+ vput(dp);
+ nfsvno_relpathbuf(&fromnd);
+ nfsvno_relpathbuf(&tond);
+ return (0);
+ }
+
+ /*
+ * Done parsing, now down to business.
+ */
+ nd->nd_repstat = nfsvno_namei(nd, &fromnd, dp, 1, exp, p, &fdirp);
+ if (nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV3) {
+ nfsrv_wcc(nd, fdirfor_ret, &fdirfor, fdiraft_ret,
+ &fdiraft);
+ nfsrv_wcc(nd, tdirfor_ret, &tdirfor, tdiraft_ret,
+ &tdiraft);
+ }
+ if (fdirp)
+ vrele(fdirp);
+ if (tdp) {
+ if (tnes.nes_vfslocked && !exp->nes_vfslocked &&
+ !(nd->nd_flag & ND_NFSV4))
+ nfsvno_unlockvfs(mp);
+ vrele(tdp);
+ }
+ nfsvno_relpathbuf(&tond);
+ return (0);
+ }
+ if (vnode_vtype(fromnd.ni_vp) == VDIR)
+ tond.ni_cnd.cn_flags |= WILLBEDIR;
+ nd->nd_repstat = nfsvno_namei(nd, &tond, tdp, 0, &tnes, p, &tdirp);
+ nd->nd_repstat = nfsvno_rename(&fromnd, &tond, nd->nd_repstat,
+ nd->nd_flag, nd->nd_cred, p);
+ if (fdirp)
+ fdiraft_ret = nfsvno_getattr(fdirp, &fdiraft, nd->nd_cred, p);
+ if (tdirp)
+ tdiraft_ret = nfsvno_getattr(tdirp, &tdiraft, nd->nd_cred, p);
+ if (tnes.nes_vfslocked && !exp->nes_vfslocked &&
+ !(nd->nd_flag & ND_NFSV4))
+ nfsvno_unlockvfs(mp);
+ if (fdirp)
+ vrele(fdirp);
+ if (tdirp)
+ vrele(tdirp);
+ if (nd->nd_flag & ND_NFSV3) {
+ nfsrv_wcc(nd, fdirfor_ret, &fdirfor, fdiraft_ret, &fdiraft);
+ nfsrv_wcc(nd, tdirfor_ret, &tdirfor, tdiraft_ret, &tdiraft);
+ } else if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, 10 * NFSX_UNSIGNED);
+ *tl++ = newnfs_false;
+ txdr_hyper(fdirfor.na_filerev, tl);
+ tl += 2;
+ txdr_hyper(fdiraft.na_filerev, tl);
+ tl += 2;
+ *tl++ = newnfs_false;
+ txdr_hyper(tdirfor.na_filerev, tl);
+ tl += 2;
+ txdr_hyper(tdiraft.na_filerev, tl);
+ }
+ return (0);
+}
+
+/*
+ * nfs link service
+ */
+APPLESTATIC int
+nfsrvd_link(struct nfsrv_descript *nd, int isdgram,
+ vnode_t vp, vnode_t tovp, NFSPROC_T *p, struct nfsexstuff *exp,
+ struct nfsexstuff *toexp)
+{
+ struct nameidata named;
+ u_int32_t *tl;
+ int error = 0, dirfor_ret = 1, diraft_ret = 1, getret = 1;
+ vnode_t dirp = NULL, dp = NULL;
+ struct nfsvattr dirfor, diraft, at;
+ struct nfsexstuff tnes;
+ struct nfsrvfh dfh;
+ mount_t mp = NULL;
+ char *bufp;
+ u_long *hashp;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, getret, &at);
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ return (0);
+ }
+ NFSVOPUNLOCK(vp, 0, p);
+ if (vnode_vtype(vp) == VDIR) {
+ if (nd->nd_flag & ND_NFSV4)
+ nd->nd_repstat = NFSERR_ISDIR;
+ else
+ nd->nd_repstat = NFSERR_INVAL;
+ if (tovp)
+ vrele(tovp);
+ } else if (vnode_vtype(vp) == VLNK) {
+ if (nd->nd_flag & ND_NFSV2)
+ nd->nd_repstat = NFSERR_INVAL;
+ else
+ nd->nd_repstat = NFSERR_NOTSUPP;
+ if (tovp)
+ vrele(tovp);
+ }
+ if (!nd->nd_repstat) {
+ if (nd->nd_flag & ND_NFSV4) {
+ dp = tovp;
+ tnes = *toexp;
+ } else {
+ error = nfsrv_mtofh(nd, &dfh);
+ if (error) {
+ vrele(vp);
+ /* tovp is always NULL unless NFSv4 */
+ return (error);
+ }
+ /* Won't lock vfs if already locked, mp == NULL */
+ tnes.nes_vfslocked = exp->nes_vfslocked;
+ nfsd_fhtovp(nd, &dfh, &dp, &tnes, &mp, 0, p);
+ if (dp)
+ NFSVOPUNLOCK(dp, 0, p);
+ }
+ }
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE, LOCKPARENT);
+ if (!nd->nd_repstat) {
+ nfsvno_setpathbuf(&named, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
+ if (error) {
+ vrele(vp);
+ if (dp) {
+ if (tnes.nes_vfslocked && !exp->nes_vfslocked &&
+ !(nd->nd_flag & ND_NFSV4))
+ nfsvno_unlockvfs(mp);
+ vrele(dp);
+ }
+ nfsvno_relpathbuf(&named);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, &tnes,
+ p, &dirp);
+ } else {
+ if (dp)
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ }
+ }
+ if (dirp) {
+ if (nd->nd_flag & ND_NFSV2) {
+ vrele(dirp);
+ dirp = NULL;
+ } else {
+ dirfor_ret = nfsvno_getattr(dirp, &dirfor,
+ nd->nd_cred, p);
+ }
+ }
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_link(&named, vp, nd->nd_cred, p, exp);
+ if (nd->nd_flag & ND_NFSV3)
+ getret = nfsvno_getattr(vp, &at, nd->nd_cred, p);
+ if (dirp) {
+ diraft_ret = nfsvno_getattr(dirp, &diraft, nd->nd_cred, p);
+ vrele(dirp);
+ }
+ if (tnes.nes_vfslocked && !exp->nes_vfslocked &&
+ !(nd->nd_flag & ND_NFSV4))
+ nfsvno_unlockvfs(mp);
+ vrele(vp);
+ if (nd->nd_flag & ND_NFSV3) {
+ nfsrv_postopattr(nd, getret, &at);
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ } else if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
+ *tl++ = newnfs_false;
+ txdr_hyper(dirfor.na_filerev, tl);
+ tl += 2;
+ txdr_hyper(diraft.na_filerev, tl);
+ }
+ return (0);
+}
+
+/*
+ * nfs symbolic link service
+ */
+APPLESTATIC int
+nfsrvd_symlink(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t dp, vnode_t *vpp, fhandle_t *fhp, NFSPROC_T *p,
+ struct nfsexstuff *exp)
+{
+ struct nfsvattr nva, dirfor, diraft;
+ struct nameidata named;
+ int error, dirfor_ret = 1, diraft_ret = 1, pathlen;
+ vnode_t dirp = NULL;
+ char *bufp, *pathcp = NULL;
+ u_long *hashp;
+
+ if (nd->nd_repstat) {
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ return (0);
+ }
+ if (vpp)
+ *vpp = NULL;
+ NFSVNO_ATTRINIT(&nva);
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE,
+ LOCKPARENT | SAVESTART);
+ nfsvno_setpathbuf(&named, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
+ if (!error && !nd->nd_repstat)
+ error = nfsvno_getsymlink(nd, &nva, p, &pathcp, &pathlen);
+ if (error) {
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp, p, &dirp);
+ } else {
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ }
+ if (dirp != NULL && !(nd->nd_flag & ND_NFSV3)) {
+ vrele(dirp);
+ dirp = NULL;
+ }
+
+ /*
+ * And call nfsrvd_symlinksub() to do the common code. It will
+ * return EBADRPC upon a parsing error, 0 otherwise.
+ */
+ if (!nd->nd_repstat) {
+ if (dirp != NULL)
+ dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd->nd_cred,
+ p);
+ nfsrvd_symlinksub(nd, &named, &nva, fhp, vpp, dirp,
+ &dirfor, &diraft, &diraft_ret, NULL, NULL, p, exp,
+ pathcp, pathlen);
+ } else if (dirp != NULL) {
+ dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd->nd_cred, p);
+ vrele(dirp);
+ }
+ if (pathcp)
+ FREE(pathcp, M_TEMP);
+
+ if (nd->nd_flag & ND_NFSV3) {
+ if (!nd->nd_repstat) {
+ (void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 1);
+ nfsrv_postopattr(nd, 0, &nva);
+ }
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ }
+ return (0);
+}
+
+/*
+ * Common code for creating a symbolic link.
+ */
+static void
+nfsrvd_symlinksub(struct nfsrv_descript *nd, struct nameidata *ndp,
+ struct nfsvattr *nvap, fhandle_t *fhp, vnode_t *vpp,
+ vnode_t dirp, struct nfsvattr *dirforp, struct nfsvattr *diraftp,
+ int *diraft_retp, nfsattrbit_t *attrbitp,
+ NFSACL_T *aclp, NFSPROC_T *p, struct nfsexstuff *exp, char *pathcp,
+ int pathlen)
+{
+ u_int32_t *tl;
+
+ nd->nd_repstat = nfsvno_symlink(ndp, nvap, pathcp, pathlen,
+ !(nd->nd_flag & ND_NFSV2), nd->nd_saveduid, nd->nd_cred, p, exp);
+ if (!nd->nd_repstat && !(nd->nd_flag & ND_NFSV2)) {
+ nfsrv_fixattr(nd, ndp->ni_vp, nvap, aclp, p, attrbitp, exp);
+ if (nd->nd_flag & ND_NFSV3) {
+ nd->nd_repstat = nfsvno_getfh(ndp->ni_vp, fhp, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_getattr(ndp->ni_vp,
+ nvap, nd->nd_cred, p);
+ }
+ if (vpp) {
+ NFSVOPUNLOCK(ndp->ni_vp, 0, p);
+ *vpp = ndp->ni_vp;
+ } else {
+ vput(ndp->ni_vp);
+ }
+ }
+ if (dirp) {
+ *diraft_retp = nfsvno_getattr(dirp, diraftp, nd->nd_cred, p);
+ vrele(dirp);
+ }
+ if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
+ *tl++ = newnfs_false;
+ txdr_hyper(dirforp->na_filerev, tl);
+ tl += 2;
+ txdr_hyper(diraftp->na_filerev, tl);
+ (void) nfsrv_putattrbit(nd, attrbitp);
+ }
+}
+
+/*
+ * nfs mkdir service
+ */
+APPLESTATIC int
+nfsrvd_mkdir(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t dp, vnode_t *vpp, fhandle_t *fhp, NFSPROC_T *p,
+ struct nfsexstuff *exp)
+{
+ struct nfsvattr nva, dirfor, diraft;
+ struct nameidata named;
+ u_int32_t *tl;
+ int error, dirfor_ret = 1, diraft_ret = 1;
+ vnode_t dirp = NULL;
+ char *bufp;
+ u_long *hashp;
+
+ if (nd->nd_repstat) {
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ return (0);
+ }
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE, LOCKPARENT);
+ nfsvno_setpathbuf(&named, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
+ if (error) {
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ NFSVNO_ATTRINIT(&nva);
+ if (nd->nd_flag & ND_NFSV3) {
+ error = nfsrv_sattr(nd, &nva, NULL, NULL, p);
+ if (error) {
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ return (error);
+ }
+ } else {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ nva.na_mode = nfstov_mode(*tl++);
+ }
+ }
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp, p, &dirp);
+ } else {
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ }
+ if (dirp != NULL && !(nd->nd_flag & ND_NFSV3)) {
+ vrele(dirp);
+ dirp = NULL;
+ }
+ if (nd->nd_repstat) {
+ if (dirp != NULL) {
+ dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd->nd_cred,
+ p);
+ vrele(dirp);
+ }
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret,
+ &diraft);
+ return (0);
+ }
+ if (dirp != NULL)
+ dirfor_ret = nfsvno_getattr(dirp, &dirfor, nd->nd_cred, p);
+
+ /*
+ * Call nfsrvd_mkdirsub() for the code common to V4 as well.
+ */
+ nfsrvd_mkdirsub(nd, &named, &nva, fhp, vpp, dirp, &dirfor, &diraft,
+ &diraft_ret, NULL, NULL, p, exp);
+
+ if (nd->nd_flag & ND_NFSV3) {
+ if (!nd->nd_repstat) {
+ (void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 1);
+ nfsrv_postopattr(nd, 0, &nva);
+ }
+ nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
+ } else if (!nd->nd_repstat) {
+ (void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 0);
+ nfsrv_fillattr(nd, &nva);
+ }
+ return (0);
+nfsmout:
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ return (error);
+}
+
+/*
+ * Code common to mkdir for V2,3 and 4.
+ */
+static void
+nfsrvd_mkdirsub(struct nfsrv_descript *nd, struct nameidata *ndp,
+ struct nfsvattr *nvap, fhandle_t *fhp, vnode_t *vpp,
+ vnode_t dirp, struct nfsvattr *dirforp, struct nfsvattr *diraftp,
+ int *diraft_retp, nfsattrbit_t *attrbitp, NFSACL_T *aclp,
+ NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ vnode_t vp;
+ u_int32_t *tl;
+
+ NFSVNO_SETATTRVAL(nvap, type, VDIR);
+ nd->nd_repstat = nfsvno_mkdir(ndp, nvap, nd->nd_saveduid,
+ nd->nd_cred, p, exp);
+ if (!nd->nd_repstat) {
+ vp = ndp->ni_vp;
+ nfsrv_fixattr(nd, vp, nvap, aclp, p, attrbitp, exp);
+ nd->nd_repstat = nfsvno_getfh(vp, fhp, p);
+ if (!(nd->nd_flag & ND_NFSV4) && !nd->nd_repstat)
+ nd->nd_repstat = nfsvno_getattr(vp, nvap, nd->nd_cred,
+ p);
+ if (vpp && !nd->nd_repstat) {
+ NFSVOPUNLOCK(vp, 0, p);
+ *vpp = vp;
+ } else {
+ vput(vp);
+ }
+ }
+ if (dirp) {
+ *diraft_retp = nfsvno_getattr(dirp, diraftp, nd->nd_cred, p);
+ vrele(dirp);
+ }
+ if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED);
+ *tl++ = newnfs_false;
+ txdr_hyper(dirforp->na_filerev, tl);
+ tl += 2;
+ txdr_hyper(diraftp->na_filerev, tl);
+ (void) nfsrv_putattrbit(nd, attrbitp);
+ }
+}
+
+/*
+ * nfs commit service
+ */
+APPLESTATIC int
+nfsrvd_commit(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ struct nfsvattr bfor, aft;
+ u_int32_t *tl;
+ int error = 0, for_ret = 1, aft_ret = 1, cnt;
+ u_int64_t off;
+
+ if (nd->nd_repstat) {
+ nfsrv_wcc(nd, for_ret, &bfor, aft_ret, &aft);
+ return (0);
+ }
+ NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
+ /*
+ * XXX At this time VOP_FSYNC() does not accept offset and byte
+ * count parameters, so these arguments are useless (someday maybe).
+ */
+ off = fxdr_hyper(tl);
+ tl += 2;
+ cnt = fxdr_unsigned(int, *tl);
+ if (nd->nd_flag & ND_NFSV3)
+ for_ret = nfsvno_getattr(vp, &bfor, nd->nd_cred, p);
+ nd->nd_repstat = nfsvno_fsync(vp, off, cnt, nd->nd_cred, p);
+ if (nd->nd_flag & ND_NFSV3) {
+ aft_ret = nfsvno_getattr(vp, &aft, nd->nd_cred, p);
+ nfsrv_wcc(nd, for_ret, &bfor, aft_ret, &aft);
+ }
+ vput(vp);
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_VERF);
+ *tl++ = txdr_unsigned(nfsboottime.tv_sec);
+ *tl = txdr_unsigned(nfsboottime.tv_usec);
+ }
+ return (0);
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfs statfs service
+ */
+APPLESTATIC int
+nfsrvd_statfs(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ struct statfs *sf;
+ u_int32_t *tl;
+ int getret = 1;
+ struct nfsvattr at;
+ struct statfs sfs;
+ u_quad_t tval;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+ sf = &sfs;
+ nd->nd_repstat = nfsvno_statfs(vp, sf, nd->nd_cred, p);
+ getret = nfsvno_getattr(vp, &at, nd->nd_cred, p);
+ vput(vp);
+ if (nd->nd_flag & ND_NFSV3)
+ nfsrv_postopattr(nd, getret, &at);
+ if (nd->nd_repstat)
+ return (0);
+ if (nd->nd_flag & ND_NFSV2) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_V2STATFS);
+ *tl++ = txdr_unsigned(NFS_V2MAXDATA);
+ *tl++ = txdr_unsigned(sf->f_bsize);
+ *tl++ = txdr_unsigned(sf->f_blocks);
+ *tl++ = txdr_unsigned(sf->f_bfree);
+ *tl = txdr_unsigned(sf->f_bavail);
+ } else {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_V3STATFS);
+ tval = (u_quad_t)sf->f_blocks;
+ tval *= (u_quad_t)sf->f_bsize;
+ txdr_hyper(tval, tl); tl += 2;
+ tval = (u_quad_t)sf->f_bfree;
+ tval *= (u_quad_t)sf->f_bsize;
+ txdr_hyper(tval, tl); tl += 2;
+ tval = (u_quad_t)sf->f_bavail;
+ tval *= (u_quad_t)sf->f_bsize;
+ txdr_hyper(tval, tl); tl += 2;
+ tval = (u_quad_t)sf->f_files;
+ txdr_hyper(tval, tl); tl += 2;
+ tval = (u_quad_t)sf->f_ffree;
+ txdr_hyper(tval, tl); tl += 2;
+ tval = (u_quad_t)sf->f_ffree;
+ txdr_hyper(tval, tl); tl += 2;
+ *tl = 0;
+ }
+ return (0);
+}
+
+/*
+ * nfs fsinfo service
+ */
+APPLESTATIC int
+nfsrvd_fsinfo(struct nfsrv_descript *nd, int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ struct nfsfsinfo fs;
+ int getret = 1;
+ struct nfsvattr at;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+ getret = nfsvno_getattr(vp, &at, nd->nd_cred, p);
+ nfsvno_getfs(&fs, isdgram);
+ vput(vp);
+ nfsrv_postopattr(nd, getret, &at);
+ NFSM_BUILD(tl, u_int32_t *, NFSX_V3FSINFO);
+ *tl++ = txdr_unsigned(fs.fs_rtmax);
+ *tl++ = txdr_unsigned(fs.fs_rtpref);
+ *tl++ = txdr_unsigned(fs.fs_rtmult);
+ *tl++ = txdr_unsigned(fs.fs_wtmax);
+ *tl++ = txdr_unsigned(fs.fs_wtpref);
+ *tl++ = txdr_unsigned(fs.fs_wtmult);
+ *tl++ = txdr_unsigned(fs.fs_dtpref);
+ txdr_hyper(fs.fs_maxfilesize, tl);
+ tl += 2;
+ txdr_nfsv3time(&fs.fs_timedelta, tl);
+ tl += 2;
+ *tl = txdr_unsigned(fs.fs_properties);
+ return (0);
+}
+
+/*
+ * nfs pathconf service
+ */
+APPLESTATIC int
+nfsrvd_pathconf(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ struct nfsv3_pathconf *pc;
+ int getret = 1;
+ register_t linkmax, namemax, chownres, notrunc;
+ struct nfsvattr at;
+
+ if (nd->nd_repstat) {
+ nfsrv_postopattr(nd, getret, &at);
+ return (0);
+ }
+ nd->nd_repstat = nfsvno_pathconf(vp, _PC_LINK_MAX, &linkmax,
+ nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_pathconf(vp, _PC_NAME_MAX, &namemax,
+ nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat=nfsvno_pathconf(vp, _PC_CHOWN_RESTRICTED,
+ &chownres, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_pathconf(vp, _PC_NO_TRUNC, &notrunc,
+ nd->nd_cred, p);
+ getret = nfsvno_getattr(vp, &at, nd->nd_cred, p);
+ vput(vp);
+ nfsrv_postopattr(nd, getret, &at);
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(pc, struct nfsv3_pathconf *, NFSX_V3PATHCONF);
+ pc->pc_linkmax = txdr_unsigned(linkmax);
+ pc->pc_namemax = txdr_unsigned(namemax);
+ pc->pc_notrunc = txdr_unsigned(notrunc);
+ pc->pc_chownrestricted = txdr_unsigned(chownres);
+
+ /*
+ * These should probably be supported by VOP_PATHCONF(), but
+ * until msdosfs is exportable (why would you want to?), the
+ * Unix defaults should be ok.
+ */
+ pc->pc_caseinsensitive = newnfs_false;
+ pc->pc_casepreserving = newnfs_true;
+ }
+ return (0);
+}
+
+/*
+ * nfsv4 lock service
+ */
+APPLESTATIC int
+nfsrvd_lock(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int i;
+ struct nfsstate *stp = NULL;
+ struct nfslock *lop;
+ struct nfslockconflict cf;
+ int error = 0;
+ u_short flags = NFSLCK_LOCK, lflags;
+ u_int64_t offset, len;
+ nfsv4stateid_t stateid;
+ nfsquad_t clientid;
+
+ NFSM_DISSECT(tl, u_int32_t *, 7 * NFSX_UNSIGNED);
+ i = fxdr_unsigned(int, *tl++);
+ switch (i) {
+ case NFSV4LOCKT_READW:
+ flags |= NFSLCK_BLOCKING;
+ case NFSV4LOCKT_READ:
+ lflags = NFSLCK_READ;
+ break;
+ case NFSV4LOCKT_WRITEW:
+ flags |= NFSLCK_BLOCKING;
+ case NFSV4LOCKT_WRITE:
+ lflags = NFSLCK_WRITE;
+ break;
+ default:
+ nd->nd_repstat = NFSERR_BADXDR;
+ goto nfsmout;
+ };
+ if (*tl++ == newnfs_true)
+ flags |= NFSLCK_RECLAIM;
+ offset = fxdr_hyper(tl);
+ tl += 2;
+ len = fxdr_hyper(tl);
+ tl += 2;
+ if (*tl == newnfs_true)
+ flags |= NFSLCK_OPENTOLOCK;
+ if (flags & NFSLCK_OPENTOLOCK) {
+ NFSM_DISSECT(tl, u_int32_t *, 5 * NFSX_UNSIGNED + NFSX_STATEID);
+ i = fxdr_unsigned(int, *(tl+4+(NFSX_STATEID / NFSX_UNSIGNED)));
+ MALLOC(stp, struct nfsstate *, sizeof (struct nfsstate) + i,
+ M_NFSDSTATE, M_WAITOK);
+ stp->ls_ownerlen = i;
+ stp->ls_op = nd->nd_rp;
+ stp->ls_seq = fxdr_unsigned(int, *tl++);
+ stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
+ NFSX_STATEIDOTHER);
+ tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
+ stp->ls_opentolockseq = fxdr_unsigned(int, *tl++);
+ clientid.lval[0] = *tl++;
+ clientid.lval[1] = *tl++;
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ error = nfsrv_mtostr(nd, stp->ls_owner, stp->ls_ownerlen);
+ if (error)
+ goto nfsmout;
+ } else {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + NFSX_UNSIGNED);
+ MALLOC(stp, struct nfsstate *, sizeof (struct nfsstate),
+ M_NFSDSTATE, M_WAITOK);
+ stp->ls_ownerlen = 0;
+ stp->ls_op = nd->nd_rp;
+ stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
+ NFSX_STATEIDOTHER);
+ tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
+ stp->ls_seq = fxdr_unsigned(int, *tl);
+ clientid.lval[0] = stp->ls_stateid.other[0];
+ clientid.lval[1] = stp->ls_stateid.other[1];
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ }
+ MALLOC(lop, struct nfslock *, sizeof (struct nfslock),
+ M_NFSDLOCK, M_WAITOK);
+ lop->lo_first = offset;
+ if (len == NFS64BITSSET) {
+ lop->lo_end = NFS64BITSSET;
+ } else {
+ lop->lo_end = offset + len;
+ if (lop->lo_end <= lop->lo_first)
+ nd->nd_repstat = NFSERR_INVAL;
+ }
+ lop->lo_flags = lflags;
+ stp->ls_flags = flags;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+
+ /*
+ * Do basic access checking.
+ */
+ if (!nd->nd_repstat && vnode_vtype(vp) != VREG) {
+ if (vnode_vtype(vp) == VDIR)
+ nd->nd_repstat = NFSERR_ISDIR;
+ else
+ nd->nd_repstat = NFSERR_INVAL;
+ }
+ if (!nd->nd_repstat) {
+ if (lflags & NFSLCK_WRITE) {
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_WRITEDATA,
+ nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
+ NFSACCCHK_VPISLOCKED);
+ } else {
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_READDATA,
+ nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
+ NFSACCCHK_VPISLOCKED);
+ if (nd->nd_repstat)
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_EXECUTE,
+ nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
+ NFSACCCHK_VPISLOCKED);
+ }
+ }
+
+ /*
+ * We call nfsrv_lockctrl() even if nd_repstat set, so that the
+ * seqid# gets updated. nfsrv_lockctrl() will return the value
+ * of nd_repstat, if it gets that far.
+ */
+ nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, &cf, clientid,
+ &stateid, exp, nd, p);
+ if (lop)
+ FREE((caddr_t)lop, M_NFSDLOCK);
+ if (stp)
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
+ *tl++ = txdr_unsigned(stateid.seqid);
+ NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
+ } else if (nd->nd_repstat == NFSERR_DENIED) {
+ NFSM_BUILD(tl, u_int32_t *, 7 * NFSX_UNSIGNED);
+ txdr_hyper(cf.cl_first, tl);
+ tl += 2;
+ if (cf.cl_end == NFS64BITSSET)
+ len = NFS64BITSSET;
+ else
+ len = cf.cl_end - cf.cl_first;
+ txdr_hyper(len, tl);
+ tl += 2;
+ if (cf.cl_flags == NFSLCK_WRITE)
+ *tl++ = txdr_unsigned(NFSV4LOCKT_WRITE);
+ else
+ *tl++ = txdr_unsigned(NFSV4LOCKT_READ);
+ *tl++ = stateid.other[0];
+ *tl = stateid.other[1];
+ (void) nfsm_strtom(nd, cf.cl_owner, cf.cl_ownerlen);
+ }
+ vput(vp);
+ return (0);
+nfsmout:
+ vput(vp);
+ if (stp)
+ free((caddr_t)stp, M_NFSDSTATE);
+ return (error);
+}
+
+/*
+ * nfsv4 lock test service
+ */
+APPLESTATIC int
+nfsrvd_lockt(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int i;
+ struct nfsstate *stp = NULL;
+ struct nfslock lo, *lop = &lo;
+ struct nfslockconflict cf;
+ int error = 0;
+ nfsv4stateid_t stateid;
+ nfsquad_t clientid;
+ u_int64_t len;
+
+ NFSM_DISSECT(tl, u_int32_t *, 8 * NFSX_UNSIGNED);
+ i = fxdr_unsigned(int, *(tl + 7));
+ MALLOC(stp, struct nfsstate *, sizeof (struct nfsstate) + i,
+ M_NFSDSTATE, M_WAITOK);
+ stp->ls_ownerlen = i;
+ stp->ls_op = NULL;
+ stp->ls_flags = NFSLCK_TEST;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ i = fxdr_unsigned(int, *tl++);
+ switch (i) {
+ case NFSV4LOCKT_READW:
+ stp->ls_flags |= NFSLCK_BLOCKING;
+ case NFSV4LOCKT_READ:
+ lo.lo_flags = NFSLCK_READ;
+ break;
+ case NFSV4LOCKT_WRITEW:
+ stp->ls_flags |= NFSLCK_BLOCKING;
+ case NFSV4LOCKT_WRITE:
+ lo.lo_flags = NFSLCK_WRITE;
+ break;
+ default:
+ nd->nd_repstat = NFSERR_BADXDR;
+ goto nfsmout;
+ };
+ lo.lo_first = fxdr_hyper(tl);
+ tl += 2;
+ len = fxdr_hyper(tl);
+ if (len == NFS64BITSSET) {
+ lo.lo_end = NFS64BITSSET;
+ } else {
+ lo.lo_end = lo.lo_first + len;
+ if (lo.lo_end <= lo.lo_first)
+ nd->nd_repstat = NFSERR_INVAL;
+ }
+ tl += 2;
+ clientid.lval[0] = *tl++;
+ clientid.lval[1] = *tl;
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ error = nfsrv_mtostr(nd, stp->ls_owner, stp->ls_ownerlen);
+ if (error)
+ goto nfsmout;
+ if (!nd->nd_repstat && vnode_vtype(vp) != VREG) {
+ if (vnode_vtype(vp) == VDIR)
+ nd->nd_repstat = NFSERR_ISDIR;
+ else
+ nd->nd_repstat = NFSERR_INVAL;
+ }
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, &cf, clientid,
+ &stateid, exp, nd, p);
+ if (stp)
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ if (nd->nd_repstat) {
+ if (nd->nd_repstat == NFSERR_DENIED) {
+ NFSM_BUILD(tl, u_int32_t *, 7 * NFSX_UNSIGNED);
+ txdr_hyper(cf.cl_first, tl);
+ tl += 2;
+ if (cf.cl_end == NFS64BITSSET)
+ len = NFS64BITSSET;
+ else
+ len = cf.cl_end - cf.cl_first;
+ txdr_hyper(len, tl);
+ tl += 2;
+ if (cf.cl_flags == NFSLCK_WRITE)
+ *tl++ = txdr_unsigned(NFSV4LOCKT_WRITE);
+ else
+ *tl++ = txdr_unsigned(NFSV4LOCKT_READ);
+ *tl++ = stp->ls_stateid.other[0];
+ *tl = stp->ls_stateid.other[1];
+ (void) nfsm_strtom(nd, cf.cl_owner, cf.cl_ownerlen);
+ }
+ }
+ vput(vp);
+ return (0);
+nfsmout:
+ vput(vp);
+ if (stp)
+ free((caddr_t)stp, M_NFSDSTATE);
+ return (error);
+}
+
+/*
+ * nfsv4 unlock service
+ */
+APPLESTATIC int
+nfsrvd_locku(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int i;
+ struct nfsstate *stp;
+ struct nfslock *lop;
+ int error = 0;
+ nfsv4stateid_t stateid;
+ nfsquad_t clientid;
+ u_int64_t len;
+
+ NFSM_DISSECT(tl, u_int32_t *, 6 * NFSX_UNSIGNED + NFSX_STATEID);
+ MALLOC(stp, struct nfsstate *, sizeof (struct nfsstate),
+ M_NFSDSTATE, M_WAITOK);
+ MALLOC(lop, struct nfslock *, sizeof (struct nfslock),
+ M_NFSDLOCK, M_WAITOK);
+ stp->ls_flags = NFSLCK_UNLOCK;
+ lop->lo_flags = NFSLCK_UNLOCK;
+ stp->ls_op = nd->nd_rp;
+ i = fxdr_unsigned(int, *tl++);
+ switch (i) {
+ case NFSV4LOCKT_READW:
+ stp->ls_flags |= NFSLCK_BLOCKING;
+ case NFSV4LOCKT_READ:
+ break;
+ case NFSV4LOCKT_WRITEW:
+ stp->ls_flags |= NFSLCK_BLOCKING;
+ case NFSV4LOCKT_WRITE:
+ break;
+ default:
+ nd->nd_repstat = NFSERR_BADXDR;
+ goto nfsmout;
+ };
+ stp->ls_ownerlen = 0;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ stp->ls_seq = fxdr_unsigned(int, *tl++);
+ stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
+ NFSX_STATEIDOTHER);
+ tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
+ lop->lo_first = fxdr_hyper(tl);
+ tl += 2;
+ len = fxdr_hyper(tl);
+ if (len == NFS64BITSSET) {
+ lop->lo_end = NFS64BITSSET;
+ } else {
+ lop->lo_end = lop->lo_first + len;
+ if (lop->lo_end <= lop->lo_first)
+ nd->nd_repstat = NFSERR_INVAL;
+ }
+ clientid.lval[0] = stp->ls_stateid.other[0];
+ clientid.lval[1] = stp->ls_stateid.other[1];
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ if (!nd->nd_repstat && vnode_vtype(vp) != VREG) {
+ if (vnode_vtype(vp) == VDIR)
+ nd->nd_repstat = NFSERR_ISDIR;
+ else
+ nd->nd_repstat = NFSERR_INVAL;
+ }
+ /*
+ * Call nfsrv_lockctrl() even if nd_repstat is set, so that the
+ * seqid# gets incremented. nfsrv_lockctrl() will return the
+ * value of nd_repstat, if it gets that far.
+ */
+ nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
+ &stateid, exp, nd, p);
+ if (stp)
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ if (lop)
+ free((caddr_t)lop, M_NFSDLOCK);
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
+ *tl++ = txdr_unsigned(stateid.seqid);
+ NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
+ }
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfsv4 open service
+ */
+APPLESTATIC int
+nfsrvd_open(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t dp, vnode_t *vpp, __unused fhandle_t *fhp, NFSPROC_T *p,
+ struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int i;
+ struct nfsstate *stp = NULL;
+ int error = 0, create, claim, exclusive_flag = 0;
+ u_int32_t rflags = NFSV4OPEN_LOCKTYPEPOSIX, acemask;
+ int how = NFSCREATE_UNCHECKED;
+ u_char cverf[NFSX_VERF];
+ vnode_t vp = NULL, dirp = NULL;
+ struct nfsvattr nva, dirfor, diraft;
+ struct nameidata named;
+ nfsv4stateid_t stateid, delegstateid;
+ nfsattrbit_t attrbits;
+ nfsquad_t clientid;
+ char *bufp = NULL;
+ u_long *hashp;
+ NFSACL_T *aclp = NULL;
+
+#ifdef NFS4_ACL_EXTATTR_NAME
+ aclp = acl_alloc();
+ aclp->acl_cnt = 0;
+#endif
+ NFSZERO_ATTRBIT(&attrbits);
+ named.ni_startdir = NULL;
+ named.ni_cnd.cn_nameiop = 0;
+ NFSM_DISSECT(tl, u_int32_t *, 6 * NFSX_UNSIGNED);
+ i = fxdr_unsigned(int, *(tl + 5));
+ MALLOC(stp, struct nfsstate *, sizeof (struct nfsstate) + i,
+ M_NFSDSTATE, M_WAITOK);
+ stp->ls_ownerlen = i;
+ stp->ls_op = nd->nd_rp;
+ stp->ls_flags = NFSLCK_OPEN;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ stp->ls_seq = fxdr_unsigned(u_int32_t, *tl++);
+ i = fxdr_unsigned(int, *tl++);
+ switch (i) {
+ case NFSV4OPEN_ACCESSREAD:
+ stp->ls_flags |= NFSLCK_READACCESS;
+ break;
+ case NFSV4OPEN_ACCESSWRITE:
+ stp->ls_flags |= NFSLCK_WRITEACCESS;
+ break;
+ case NFSV4OPEN_ACCESSBOTH:
+ stp->ls_flags |= (NFSLCK_READACCESS | NFSLCK_WRITEACCESS);
+ break;
+ default:
+ nd->nd_repstat = NFSERR_INVAL;
+ };
+ i = fxdr_unsigned(int, *tl++);
+ switch (i) {
+ case NFSV4OPEN_DENYNONE:
+ break;
+ case NFSV4OPEN_DENYREAD:
+ stp->ls_flags |= NFSLCK_READDENY;
+ break;
+ case NFSV4OPEN_DENYWRITE:
+ stp->ls_flags |= NFSLCK_WRITEDENY;
+ break;
+ case NFSV4OPEN_DENYBOTH:
+ stp->ls_flags |= (NFSLCK_READDENY | NFSLCK_WRITEDENY);
+ break;
+ default:
+ nd->nd_repstat = NFSERR_INVAL;
+ };
+ clientid.lval[0] = *tl++;
+ clientid.lval[1] = *tl;
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ error = nfsrv_mtostr(nd, stp->ls_owner, stp->ls_ownerlen);
+ if (error) {
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ return (error);
+ }
+ NFSVNO_ATTRINIT(&nva);
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ create = fxdr_unsigned(int, *tl);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_getattr(dp, &dirfor, nd->nd_cred, p);
+ if (create == NFSV4OPEN_CREATE) {
+ nva.na_type = VREG;
+ nva.na_mode = 0;
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ how = fxdr_unsigned(int, *tl);
+ switch (how) {
+ case NFSCREATE_UNCHECKED:
+ case NFSCREATE_GUARDED:
+ error = nfsv4_sattr(nd, &nva, &attrbits, aclp, p);
+ if (error) {
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ return (error);
+ }
+ /*
+ * If the na_gid being set is the same as that of
+ * the directory it is going in, clear it, since
+ * that is what will be set by default. This allows
+ * a user that isn't in that group to do the create.
+ */
+ if (!nd->nd_repstat && NFSVNO_ISSETGID(&nva) &&
+ nva.na_gid == dirfor.na_gid)
+ NFSVNO_UNSET(&nva, gid);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsrv_checkuidgid(nd, &nva);
+ break;
+ case NFSCREATE_EXCLUSIVE:
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_VERF);
+ NFSBCOPY((caddr_t)tl, cverf, NFSX_VERF);
+ break;
+ default:
+ nd->nd_repstat = NFSERR_BADXDR;
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ return (0);
+ };
+ } else if (create != NFSV4OPEN_NOCREATE) {
+ nd->nd_repstat = NFSERR_BADXDR;
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ return (0);
+ }
+
+ /*
+ * Now, handle the claim, which usually includes looking up a
+ * name in the directory referenced by dp. The exception is
+ * NFSV4OPEN_CLAIMPREVIOUS.
+ */
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ claim = fxdr_unsigned(int, *tl);
+ if (claim == NFSV4OPEN_CLAIMDELEGATECUR) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID);
+ stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ NFSBCOPY((caddr_t)tl,(caddr_t)stateid.other,NFSX_STATEIDOTHER);
+ stp->ls_flags |= NFSLCK_DELEGCUR;
+ } else if (claim == NFSV4OPEN_CLAIMDELEGATEPREV) {
+ stp->ls_flags |= NFSLCK_DELEGPREV;
+ }
+ if (claim == NFSV4OPEN_CLAIMNULL || claim == NFSV4OPEN_CLAIMDELEGATECUR
+ || claim == NFSV4OPEN_CLAIMDELEGATEPREV) {
+ if (!nd->nd_repstat && create == NFSV4OPEN_CREATE &&
+ claim != NFSV4OPEN_CLAIMNULL)
+ nd->nd_repstat = NFSERR_INVAL;
+ if (nd->nd_repstat) {
+ nd->nd_repstat = nfsrv_opencheck(clientid,
+ &stateid, stp, NULL, nd, p, nd->nd_repstat);
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ return (0);
+ }
+ if (create == NFSV4OPEN_CREATE)
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, CREATE,
+ LOCKPARENT | LOCKLEAF | SAVESTART);
+ else
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, LOOKUP,
+ LOCKLEAF | SAVESTART);
+ nfsvno_setpathbuf(&named, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
+ if (error) {
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ nfsvno_relpathbuf(&named);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsvno_namei(nd, &named, dp, 0, exp,
+ p, &dirp);
+ } else {
+ vrele(dp);
+ nfsvno_relpathbuf(&named);
+ }
+ if (create == NFSV4OPEN_CREATE) {
+ switch (how) {
+ case NFSCREATE_UNCHECKED:
+ if (named.ni_vp) {
+ /*
+ * Clear the setable attribute bits, except
+ * for Size, if it is being truncated.
+ */
+ NFSZERO_ATTRBIT(&attrbits);
+ if (NFSVNO_ISSETSIZE(&nva))
+ NFSSETBIT_ATTRBIT(&attrbits,
+ NFSATTRBIT_SIZE);
+ }
+ break;
+ case NFSCREATE_GUARDED:
+ if (named.ni_vp && !nd->nd_repstat)
+ nd->nd_repstat = EEXIST;
+ break;
+ case NFSCREATE_EXCLUSIVE:
+ exclusive_flag = 1;
+ if (!named.ni_vp)
+ nva.na_mode = 0;
+ };
+ }
+ nfsvno_open(nd, &named, clientid, &stateid, stp,
+ &exclusive_flag, &nva, cverf, create, aclp, &attrbits,
+ nd->nd_cred, p, exp, &vp);
+ } else if (claim == NFSV4OPEN_CLAIMPREVIOUS) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ i = fxdr_unsigned(int, *tl);
+ switch (i) {
+ case NFSV4OPEN_DELEGATEREAD:
+ stp->ls_flags |= NFSLCK_DELEGREAD;
+ break;
+ case NFSV4OPEN_DELEGATEWRITE:
+ stp->ls_flags |= NFSLCK_DELEGWRITE;
+ case NFSV4OPEN_DELEGATENONE:
+ break;
+ default:
+ nd->nd_repstat = NFSERR_BADXDR;
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ return (0);
+ };
+ stp->ls_flags |= NFSLCK_RECLAIM;
+ vp = dp;
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ nd->nd_repstat = nfsrv_opencheck(clientid, &stateid, stp, vp,
+ nd, p, nd->nd_repstat);
+ } else {
+ nd->nd_repstat = NFSERR_BADXDR;
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ return (0);
+ }
+
+ /*
+ * Do basic access checking.
+ */
+ if (!nd->nd_repstat && vnode_vtype(vp) != VREG) {
+ if (vnode_vtype(vp) == VDIR)
+ nd->nd_repstat = NFSERR_ISDIR;
+ else if (vnode_vtype(vp) == VLNK)
+ nd->nd_repstat = NFSERR_SYMLINK;
+ else
+ nd->nd_repstat = NFSERR_INVAL;
+ }
+ if (!nd->nd_repstat && (stp->ls_flags & NFSLCK_WRITEACCESS))
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_WRITEDATA, nd->nd_cred,
+ exp, p, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+ if (!nd->nd_repstat && (stp->ls_flags & NFSLCK_READACCESS)) {
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_READDATA, nd->nd_cred,
+ exp, p, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+ if (nd->nd_repstat)
+ nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_EXECUTE,
+ nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
+ NFSACCCHK_VPISLOCKED);
+ }
+
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ if (!nd->nd_repstat && exclusive_flag &&
+ NFSBCMP(cverf, (caddr_t)&nva.na_atime, NFSX_VERF))
+ nd->nd_repstat = EEXIST;
+ /*
+ * Do the open locking/delegation stuff.
+ */
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsrv_openctrl(nd, vp, &stp, clientid, &stateid,
+ &delegstateid, &rflags, exp, p, nva.na_filerev);
+
+ /*
+ * vp must be unlocked before the call to nfsvno_getattr(dirp,...)
+ * below, to avoid a deadlock with the lookup in nfsvno_namei() above.
+ * (ie: Leave the NFSVOPUNLOCK() about here.)
+ */
+ if (vp)
+ NFSVOPUNLOCK(vp, 0, p);
+ if (stp)
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ if (!nd->nd_repstat && dirp)
+ nd->nd_repstat = nfsvno_getattr(dirp, &diraft, nd->nd_cred, p);
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID + 6 * NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(stateid.seqid);
+ NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
+ tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
+ if (claim == NFSV4OPEN_CLAIMPREVIOUS) {
+ *tl++ = newnfs_true;
+ *tl++ = 0;
+ *tl++ = 0;
+ *tl++ = 0;
+ *tl++ = 0;
+ } else {
+ *tl++ = newnfs_false; /* Since dirp is not locked */
+ txdr_hyper(dirfor.na_filerev, tl);
+ tl += 2;
+ txdr_hyper(diraft.na_filerev, tl);
+ tl += 2;
+ }
+ *tl = txdr_unsigned(rflags & NFSV4OPEN_RFLAGS);
+ (void) nfsrv_putattrbit(nd, &attrbits);
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (rflags & NFSV4OPEN_READDELEGATE)
+ *tl = txdr_unsigned(NFSV4OPEN_DELEGATEREAD);
+ else if (rflags & NFSV4OPEN_WRITEDELEGATE)
+ *tl = txdr_unsigned(NFSV4OPEN_DELEGATEWRITE);
+ else
+ *tl = txdr_unsigned(NFSV4OPEN_DELEGATENONE);
+ if (rflags & (NFSV4OPEN_READDELEGATE|NFSV4OPEN_WRITEDELEGATE)) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID+NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(delegstateid.seqid);
+ NFSBCOPY((caddr_t)delegstateid.other, (caddr_t)tl,
+ NFSX_STATEIDOTHER);
+ tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
+ if (rflags & NFSV4OPEN_RECALL)
+ *tl = newnfs_true;
+ else
+ *tl = newnfs_false;
+ if (rflags & NFSV4OPEN_WRITEDELEGATE) {
+ NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(NFSV4OPEN_LIMITSIZE);
+ txdr_hyper(nva.na_size, tl);
+ }
+ NFSM_BUILD(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(NFSV4ACE_ALLOWEDTYPE);
+ *tl++ = txdr_unsigned(0x0);
+ acemask = NFSV4ACE_ALLFILESMASK;
+ if (nva.na_mode & S_IRUSR)
+ acemask |= NFSV4ACE_READMASK;
+ if (nva.na_mode & S_IWUSR)
+ acemask |= NFSV4ACE_WRITEMASK;
+ if (nva.na_mode & S_IXUSR)
+ acemask |= NFSV4ACE_EXECUTEMASK;
+ *tl = txdr_unsigned(acemask);
+ (void) nfsm_strtom(nd, "OWNER@", 6);
+ }
+ *vpp = vp;
+ } else if (vp) {
+ vrele(vp);
+ }
+ if (dirp)
+ vrele(dirp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ return (0);
+nfsmout:
+ vrele(dp);
+#ifdef NFS4_ACL_EXTATTR_NAME
+ acl_free(aclp);
+#endif
+ if (stp)
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ return (error);
+}
+
+/*
+ * nfsv4 close service
+ */
+APPLESTATIC int
+nfsrvd_close(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ struct nfsstate st, *stp = &st;
+ int error = 0;
+ nfsv4stateid_t stateid;
+ nfsquad_t clientid;
+
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED + NFSX_STATEID);
+ stp->ls_seq = fxdr_unsigned(u_int32_t, *tl++);
+ stp->ls_ownerlen = 0;
+ stp->ls_op = nd->nd_rp;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
+ NFSX_STATEIDOTHER);
+ stp->ls_flags = NFSLCK_CLOSE;
+ clientid.lval[0] = stp->ls_stateid.other[0];
+ clientid.lval[1] = stp->ls_stateid.other[1];
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ nd->nd_repstat = nfsrv_openupdate(vp, stp, clientid, &stateid, nd, p);
+ vput(vp);
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
+ *tl++ = txdr_unsigned(stateid.seqid);
+ NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
+ }
+ return (0);
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfsv4 delegpurge service
+ */
+APPLESTATIC int
+nfsrvd_delegpurge(struct nfsrv_descript *nd, __unused int isdgram,
+ __unused vnode_t vp, __unused NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int error = 0;
+ nfsquad_t clientid;
+
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ clientid.lval[0] = *tl++;
+ clientid.lval[1] = *tl;
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ nd->nd_repstat = nfsrv_delegupdate(clientid, NULL, NULL,
+ NFSV4OP_DELEGPURGE, nd->nd_cred, p);
+nfsmout:
+ return (error);
+}
+
+/*
+ * nfsv4 delegreturn service
+ */
+APPLESTATIC int
+nfsrvd_delegreturn(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int error = 0;
+ nfsv4stateid_t stateid;
+ nfsquad_t clientid;
+
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID);
+ stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ NFSBCOPY((caddr_t)tl, (caddr_t)stateid.other, NFSX_STATEIDOTHER);
+ clientid.lval[0] = stateid.other[0];
+ clientid.lval[1] = stateid.other[1];
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ nd->nd_repstat = nfsrv_delegupdate(clientid, &stateid, vp,
+ NFSV4OP_DELEGRETURN, nd->nd_cred, p);
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfsv4 get file handle service
+ */
+APPLESTATIC int
+nfsrvd_getfh(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ fhandle_t fh;
+
+ nd->nd_repstat = nfsvno_getfh(vp, &fh, p);
+ vput(vp);
+ if (!nd->nd_repstat)
+ (void) nfsm_fhtom(nd, (u_int8_t *)&fh, 0, 0);
+ return (0);
+}
+
+/*
+ * nfsv4 open confirm service
+ */
+APPLESTATIC int
+nfsrvd_openconfirm(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ struct nfsstate st, *stp = &st;
+ int error = 0;
+ nfsv4stateid_t stateid;
+ nfsquad_t clientid;
+
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + NFSX_UNSIGNED);
+ stp->ls_ownerlen = 0;
+ stp->ls_op = nd->nd_rp;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
+ NFSX_STATEIDOTHER);
+ tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
+ stp->ls_seq = fxdr_unsigned(u_int32_t, *tl);
+ stp->ls_flags = NFSLCK_CONFIRM;
+ clientid.lval[0] = stp->ls_stateid.other[0];
+ clientid.lval[1] = stp->ls_stateid.other[1];
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ nd->nd_repstat = nfsrv_openupdate(vp, stp, clientid, &stateid, nd, p);
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
+ *tl++ = txdr_unsigned(stateid.seqid);
+ NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
+ }
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfsv4 open downgrade service
+ */
+APPLESTATIC int
+nfsrvd_opendowngrade(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int i;
+ struct nfsstate st, *stp = &st;
+ int error = 0;
+ nfsv4stateid_t stateid;
+ nfsquad_t clientid;
+
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_STATEID + 3 * NFSX_UNSIGNED);
+ stp->ls_ownerlen = 0;
+ stp->ls_op = nd->nd_rp;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ stp->ls_stateid.seqid = fxdr_unsigned(u_int32_t, *tl++);
+ NFSBCOPY((caddr_t)tl, (caddr_t)stp->ls_stateid.other,
+ NFSX_STATEIDOTHER);
+ tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
+ stp->ls_seq = fxdr_unsigned(u_int32_t, *tl++);
+ i = fxdr_unsigned(int, *tl++);
+ switch (i) {
+ case NFSV4OPEN_ACCESSREAD:
+ stp->ls_flags = (NFSLCK_READACCESS | NFSLCK_DOWNGRADE);
+ break;
+ case NFSV4OPEN_ACCESSWRITE:
+ stp->ls_flags = (NFSLCK_WRITEACCESS | NFSLCK_DOWNGRADE);
+ break;
+ case NFSV4OPEN_ACCESSBOTH:
+ stp->ls_flags = (NFSLCK_READACCESS | NFSLCK_WRITEACCESS |
+ NFSLCK_DOWNGRADE);
+ break;
+ default:
+ nd->nd_repstat = NFSERR_BADXDR;
+ };
+ i = fxdr_unsigned(int, *tl);
+ switch (i) {
+ case NFSV4OPEN_DENYNONE:
+ break;
+ case NFSV4OPEN_DENYREAD:
+ stp->ls_flags |= NFSLCK_READDENY;
+ break;
+ case NFSV4OPEN_DENYWRITE:
+ stp->ls_flags |= NFSLCK_WRITEDENY;
+ break;
+ case NFSV4OPEN_DENYBOTH:
+ stp->ls_flags |= (NFSLCK_READDENY | NFSLCK_WRITEDENY);
+ break;
+ default:
+ nd->nd_repstat = NFSERR_BADXDR;
+ };
+
+ clientid.lval[0] = stp->ls_stateid.other[0];
+ clientid.lval[1] = stp->ls_stateid.other[1];
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsrv_openupdate(vp, stp, clientid, &stateid,
+ nd, p);
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_STATEID);
+ *tl++ = txdr_unsigned(stateid.seqid);
+ NFSBCOPY((caddr_t)stateid.other,(caddr_t)tl,NFSX_STATEIDOTHER);
+ }
+nfsmout:
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfsv4 renew lease service
+ */
+APPLESTATIC int
+nfsrvd_renew(struct nfsrv_descript *nd, __unused int isdgram,
+ __unused vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int error = 0;
+ nfsquad_t clientid;
+
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_HYPER);
+ clientid.lval[0] = *tl++;
+ clientid.lval[1] = *tl;
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ nd->nd_repstat = nfsrv_getclient(clientid, (CLOPS_RENEWOP|CLOPS_RENEW),
+ NULL, (nfsquad_t)((u_quad_t)0), nd, p);
+nfsmout:
+ return (error);
+}
+
+/*
+ * nfsv4 security info service
+ */
+APPLESTATIC int
+nfsrvd_secinfo(struct nfsrv_descript *nd, int isdgram,
+ vnode_t dp, NFSPROC_T *p, struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int len;
+ struct nameidata named;
+ vnode_t dirp = NULL, vp;
+ struct nfsrvfh fh;
+ struct nfsexstuff retnes;
+ mount_t mp;
+ u_int32_t *sizp;
+ int error, savflag, i;
+ char *bufp;
+ u_long *hashp;
+
+ /*
+ * All this just to get the export flags for the name.
+ */
+ NFSNAMEICNDSET(&named.ni_cnd, nd->nd_cred, LOOKUP,
+ LOCKLEAF | SAVESTART);
+ nfsvno_setpathbuf(&named, &bufp, &hashp);
+ error = nfsrv_parsename(nd, bufp, hashp, &named.ni_pathlen);
+ if (error) {
+ vput(dp);
+ nfsvno_relpathbuf(&named);
+ return (error);
+ }
+ if (!nd->nd_repstat) {
+ nd->nd_repstat = nfsvno_namei(nd, &named, dp, 1, exp, p, &dirp);
+ } else {
+ vput(dp);
+ nfsvno_relpathbuf(&named);
+ }
+ if (dirp)
+ vrele(dirp);
+ if (nd->nd_repstat)
+ return (0);
+ vrele(named.ni_startdir);
+ nfsvno_relpathbuf(&named);
+ fh.nfsrvfh_len = NFSX_MYFH;
+ vp = named.ni_vp;
+ nd->nd_repstat = nfsvno_getfh(vp, (fhandle_t *)fh.nfsrvfh_data, p);
+ mp = vnode_mount(vp); /* so it won't try to re-lock filesys */
+ retnes.nes_vfslocked = exp->nes_vfslocked;
+ vput(vp);
+ savflag = nd->nd_flag;
+ nd->nd_flag |= ND_GSS; /* so nfsd_fhtovp() won't reply Wrongsec */
+ if (!nd->nd_repstat) {
+ nfsd_fhtovp(nd, &fh, &vp, &retnes, &mp, 0, p);
+ if (vp)
+ vput(vp);
+ }
+ nd->nd_flag = savflag;
+ if (nd->nd_repstat)
+ return (0);
+
+ /*
+ * Finally have the export flags for name, so we can create
+ * the security info.
+ */
+ len = 0;
+ NFSM_BUILD(sizp, u_int32_t *, NFSX_UNSIGNED);
+ if (!NFSVNO_EXGSSONLY(&retnes)) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl = txdr_unsigned(RPCAUTH_UNIX);
+ len++;
+ }
+ for (i = RPCAUTHGSS_SVCNONE; i <= RPCAUTHGSS_SVCPRIVACY; i++) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(RPCAUTH_GSS);
+ (void) nfsm_strtom(nd, nfsgss_mechlist[KERBV_MECH].str,
+ nfsgss_mechlist[KERBV_MECH].len);
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(GSS_KERBV_QOP);
+ *tl = txdr_unsigned(i);
+ len++;
+ }
+ *sizp = txdr_unsigned(len);
+ return (0);
+}
+
+/*
+ * nfsv4 set client id service
+ */
+APPLESTATIC int
+nfsrvd_setclientid(struct nfsrv_descript *nd, __unused int isdgram,
+ __unused vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int i;
+ int error = 0, idlen;
+ struct nfsclient *clp = NULL;
+ struct sockaddr_in *rad;
+ u_char *verf, *ucp, *ucp2, addrbuf[24];
+ nfsquad_t clientid, confirm;
+
+ if ((!nfs_rootfhset && !nfsv4root_set) ||
+ (nd->nd_flag & (ND_GSS | ND_EXGSSONLY)) == ND_EXGSSONLY) {
+ nd->nd_repstat = NFSERR_WRONGSEC;
+ return (0);
+ }
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_VERF + NFSX_UNSIGNED);
+ verf = (u_char *)tl;
+ tl += (NFSX_VERF / NFSX_UNSIGNED);
+ i = fxdr_unsigned(int, *tl);
+ if (i > NFSV4_OPAQUELIMIT || i <= 0) {
+ nd->nd_repstat = NFSERR_BADXDR;
+ return (error);
+ }
+ idlen = i;
+ if (nd->nd_flag & ND_GSS)
+ i += nd->nd_princlen;
+ MALLOC(clp, struct nfsclient *, sizeof (struct nfsclient) + i,
+ M_NFSDCLIENT, M_WAITOK);
+ NFSBZERO((caddr_t)clp, sizeof (struct nfsclient) + i);
+ NFSINITSOCKMUTEX(&clp->lc_req.nr_mtx);
+ NFSSOCKADDRALLOC(clp->lc_req.nr_nam);
+ NFSSOCKADDRSIZE(clp->lc_req.nr_nam, sizeof (struct sockaddr_in));
+ clp->lc_req.nr_cred = NULL;
+ NFSBCOPY(verf, clp->lc_verf, NFSX_VERF);
+ clp->lc_idlen = idlen;
+ error = nfsrv_mtostr(nd, clp->lc_id, idlen);
+ if (error)
+ goto nfsmout;
+ if (nd->nd_flag & ND_GSS) {
+ clp->lc_flags = LCL_GSS;
+ if (nd->nd_flag & ND_GSSINTEGRITY)
+ clp->lc_flags |= LCL_GSSINTEGRITY;
+ else if (nd->nd_flag & ND_GSSPRIVACY)
+ clp->lc_flags |= LCL_GSSPRIVACY;
+ } else {
+ clp->lc_flags = 0;
+ }
+ if ((nd->nd_flag & ND_GSS) && nd->nd_princlen > 0) {
+ clp->lc_flags |= LCL_NAME;
+ clp->lc_namelen = nd->nd_princlen;
+ clp->lc_name = &clp->lc_id[idlen];
+ NFSBCOPY(nd->nd_principal, clp->lc_name, clp->lc_namelen);
+ } else {
+ clp->lc_uid = nd->nd_cred->cr_uid;
+ clp->lc_gid = nd->nd_cred->cr_gid;
+ }
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ clp->lc_program = fxdr_unsigned(u_int32_t, *tl);
+ error = nfsrv_getclientipaddr(nd, clp);
+ if (error)
+ goto nfsmout;
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ clp->lc_callback = fxdr_unsigned(u_int32_t, *tl);
+
+ /*
+ * nfsrv_setclient() does the actual work of adding it to the
+ * client list. If there is no error, the structure has been
+ * linked into the client list and clp should no longer be used
+ * here. When an error is returned, it has not been linked in,
+ * so it should be free'd.
+ */
+ nd->nd_repstat = nfsrv_setclient(nd, &clp, &clientid, &confirm, p);
+ if (nd->nd_repstat == NFSERR_CLIDINUSE) {
+ if (clp->lc_flags & LCL_TCPCALLBACK)
+ (void) nfsm_strtom(nd, "tcp", 3);
+ else
+ (void) nfsm_strtom(nd, "udp", 3);
+ rad = NFSSOCKADDR(clp->lc_req.nr_nam, struct sockaddr_in *);
+ ucp = (u_char *)&rad->sin_addr.s_addr;
+ ucp2 = (u_char *)&rad->sin_port;
+ sprintf(addrbuf, "%d.%d.%d.%d.%d.%d", ucp[0] & 0xff,
+ ucp[1] & 0xff, ucp[2] & 0xff, ucp[3] & 0xff,
+ ucp2[0] & 0xff, ucp2[1] & 0xff);
+ (void) nfsm_strtom(nd, addrbuf, strlen(addrbuf));
+ }
+ if (clp) {
+ NFSSOCKADDRFREE(clp->lc_req.nr_nam);
+ NFSFREEMUTEX(&clp->lc_req.nr_mtx);
+ free((caddr_t)clp, M_NFSDCLIENT);
+ }
+ if (!nd->nd_repstat) {
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_HYPER);
+ *tl++ = clientid.lval[0];
+ *tl++ = clientid.lval[1];
+ *tl++ = confirm.lval[0];
+ *tl = confirm.lval[1];
+ }
+ return (0);
+nfsmout:
+ if (clp) {
+ NFSSOCKADDRFREE(clp->lc_req.nr_nam);
+ NFSFREEMUTEX(&clp->lc_req.nr_mtx);
+ free((caddr_t)clp, M_NFSDCLIENT);
+ }
+ return (error);
+}
+
+/*
+ * nfsv4 set client id confirm service
+ */
+APPLESTATIC int
+nfsrvd_setclientidcfrm(struct nfsrv_descript *nd,
+ __unused int isdgram, __unused vnode_t vp, NFSPROC_T *p,
+ __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int error = 0;
+ nfsquad_t clientid, confirm;
+
+ if ((!nfs_rootfhset && !nfsv4root_set) ||
+ (nd->nd_flag & (ND_GSS | ND_EXGSSONLY)) == ND_EXGSSONLY) {
+ nd->nd_repstat = NFSERR_WRONGSEC;
+ return (0);
+ }
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_HYPER);
+ clientid.lval[0] = *tl++;
+ clientid.lval[1] = *tl++;
+ confirm.lval[0] = *tl++;
+ confirm.lval[1] = *tl;
+
+ /*
+ * nfsrv_getclient() searches the client list for a match and
+ * returns the appropriate NFSERR status.
+ */
+ nd->nd_repstat = nfsrv_getclient(clientid, (CLOPS_CONFIRM|CLOPS_RENEW),
+ NULL, confirm, nd, p);
+nfsmout:
+ return (error);
+}
+
+/*
+ * nfsv4 verify service
+ */
+APPLESTATIC int
+nfsrvd_verify(struct nfsrv_descript *nd, int isdgram,
+ vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ int error = 0, ret, fhsize = NFSX_MYFH;
+ struct nfsvattr nva;
+ struct statfs sf;
+ struct nfsfsinfo fs;
+ fhandle_t fh;
+
+ nd->nd_repstat = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_statfs(vp, &sf, nd->nd_cred, p);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsvno_getfh(vp, &fh, p);
+ if (!nd->nd_repstat) {
+ nfsvno_getfs(&fs, isdgram);
+ error = nfsv4_loadattr(nd, vp, &nva, NULL, &fh, fhsize, NULL,
+ &sf, NULL, &fs, NULL, 1, &ret, NULL, NULL, p, nd->nd_cred);
+ if (!error) {
+ if (nd->nd_procnum == NFSV4OP_NVERIFY) {
+ if (ret == 0)
+ nd->nd_repstat = NFSERR_SAME;
+ else if (ret != NFSERR_NOTSAME)
+ nd->nd_repstat = ret;
+ } else if (ret)
+ nd->nd_repstat = ret;
+ }
+ }
+ vput(vp);
+ return (error);
+}
+
+/*
+ * nfs openattr rpc
+ */
+APPLESTATIC int
+nfsrvd_openattr(struct nfsrv_descript *nd, __unused int isdgram,
+ vnode_t dp, __unused vnode_t *vpp, __unused fhandle_t *fhp,
+ __unused NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ int error = 0, createdir;
+
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ createdir = fxdr_unsigned(int, *tl);
+ nd->nd_repstat = NFSERR_NOTSUPP;
+nfsmout:
+ vrele(dp);
+ return (error);
+}
+
+/*
+ * nfsv4 release lock owner service
+ */
+APPLESTATIC int
+nfsrvd_releaselckown(struct nfsrv_descript *nd, __unused int isdgram,
+ __unused vnode_t vp, NFSPROC_T *p, __unused struct nfsexstuff *exp)
+{
+ u_int32_t *tl;
+ struct nfsstate *stp = NULL;
+ int error = 0, len;
+ nfsquad_t clientid;
+
+ NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
+ len = fxdr_unsigned(int, *(tl + 2));
+ MALLOC(stp, struct nfsstate *, sizeof (struct nfsstate) + len,
+ M_NFSDSTATE, M_WAITOK);
+ stp->ls_ownerlen = len;
+ stp->ls_op = NULL;
+ stp->ls_flags = NFSLCK_RELEASE;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ clientid.lval[0] = *tl++;
+ clientid.lval[1] = *tl;
+ if (nd->nd_flag & ND_IMPLIEDCLID) {
+ if (nd->nd_clientid.qval != clientid.qval)
+ printf("EEK! multiple clids\n");
+ } else {
+ nd->nd_flag |= ND_IMPLIEDCLID;
+ nd->nd_clientid.qval = clientid.qval;
+ }
+ error = nfsrv_mtostr(nd, stp->ls_owner, len);
+ if (error)
+ goto nfsmout;
+ nd->nd_repstat = nfsrv_releaselckown(stp, clientid, p);
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ return (0);
+nfsmout:
+ if (stp)
+ free((caddr_t)stp, M_NFSDSTATE);
+ return (error);
+}
diff --git a/sys/fs/nfsserver/nfs_nfsdsocket.c b/sys/fs/nfsserver/nfs_nfsdsocket.c
new file mode 100644
index 0000000..29592a9
--- /dev/null
+++ b/sys/fs/nfsserver/nfs_nfsdsocket.c
@@ -0,0 +1,979 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Socket operations for use by the nfs server.
+ */
+
+#ifndef APPLEKEXT
+#include <fs/nfs/nfsport.h>
+
+extern struct nfsstats newnfsstats;
+extern struct nfsrvfh nfs_pubfh, nfs_rootfh;
+extern int nfs_pubfhset, nfs_rootfhset;
+extern struct nfsv4lock nfsv4rootfs_lock;
+extern struct nfsrv_stablefirst nfsrv_stablefirst;
+extern struct nfsclienthashhead nfsclienthash[NFSCLIENTHASHSIZE];
+extern int nfsrc_floodlevel, nfsrc_tcpsavedreplies;
+NFSV4ROOTLOCKMUTEX;
+NFSSTATESPINLOCK;
+vnode_t nfsv4root_vp = NULL;
+int nfsv4root_set = 0;
+
+int (*nfsrv3_procs0[NFS_V3NPROCS])(struct nfsrv_descript *,
+ int, vnode_t , NFSPROC_T *, struct nfsexstuff *) = {
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_getattr,
+ nfsrvd_setattr,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_access,
+ nfsrvd_readlink,
+ nfsrvd_read,
+ nfsrvd_write,
+ nfsrvd_create,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_remove,
+ nfsrvd_remove,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_readdir,
+ nfsrvd_readdirplus,
+ nfsrvd_statfs,
+ nfsrvd_fsinfo,
+ nfsrvd_pathconf,
+ nfsrvd_commit,
+};
+
+int (*nfsrv3_procs1[NFS_V3NPROCS])(struct nfsrv_descript *,
+ int, vnode_t , vnode_t *, fhandle_t *,
+ NFSPROC_T *, struct nfsexstuff *) = {
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_lookup,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_mkdir,
+ nfsrvd_symlink,
+ nfsrvd_mknod,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+};
+
+int (*nfsrv3_procs2[NFS_V3NPROCS])(struct nfsrv_descript *,
+ int, vnode_t , vnode_t , NFSPROC_T *,
+ struct nfsexstuff *, struct nfsexstuff *) = {
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ nfsrvd_rename,
+ nfsrvd_link,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+};
+
+int (*nfsrv4_ops0[NFSV4OP_NOPS])(struct nfsrv_descript *,
+ int, vnode_t , NFSPROC_T *, struct nfsexstuff *) = {
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_access,
+ nfsrvd_close,
+ nfsrvd_commit,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_delegpurge,
+ nfsrvd_delegreturn,
+ nfsrvd_getattr,
+ nfsrvd_getfh,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_lock,
+ nfsrvd_lockt,
+ nfsrvd_locku,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_verify,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_openconfirm,
+ nfsrvd_opendowngrade,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_read,
+ nfsrvd_readdirplus,
+ nfsrvd_readlink,
+ nfsrvd_remove,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_renew,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_secinfo,
+ nfsrvd_setattr,
+ nfsrvd_setclientid,
+ nfsrvd_setclientidcfrm,
+ nfsrvd_verify,
+ nfsrvd_write,
+ nfsrvd_releaselckown,
+};
+
+int (*nfsrv4_ops1[NFSV4OP_NOPS])(struct nfsrv_descript *,
+ int, vnode_t , vnode_t *, fhandle_t *,
+ NFSPROC_T *, struct nfsexstuff *) = {
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_mknod,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_lookup,
+ nfsrvd_lookup,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ nfsrvd_open,
+ nfsrvd_openattr,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t *, fhandle_t *, NFSPROC_T *, struct nfsexstuff *))0,
+};
+
+int (*nfsrv4_ops2[NFSV4OP_NOPS])(struct nfsrv_descript *,
+ int, vnode_t , vnode_t , NFSPROC_T *,
+ struct nfsexstuff *, struct nfsexstuff *) = {
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ nfsrvd_link,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ nfsrvd_rename,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+ (int (*)(struct nfsrv_descript *, int, vnode_t , vnode_t , NFSPROC_T *, struct nfsexstuff *, struct nfsexstuff *))0,
+};
+#endif /* !APPLEKEXT */
+
+/*
+ * Static array that defines which nfs rpc's are nonidempotent
+ */
+static int nfsrv_nonidempotent[NFS_V3NPROCS] = {
+ FALSE,
+ FALSE,
+ TRUE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ TRUE,
+ TRUE,
+ TRUE,
+ TRUE,
+ TRUE,
+ TRUE,
+ TRUE,
+ TRUE,
+ TRUE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+};
+
+/*
+ * This static array indicates whether or not the RPC modifies the
+ * file system.
+ */
+static int nfs_writerpc[NFS_NPROCS] = { 0, 0, 1, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+
+/* local functions */
+static void nfsrvd_compound(struct nfsrv_descript *nd, int isdgram,
+ NFSPROC_T *p);
+
+
+/*
+ * This static array indicates which server procedures require the extra
+ * arguments to return the current file handle for V2, 3.
+ */
+static int nfs_retfh[NFS_V3NPROCS] = { 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1,
+ 1, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0 };
+
+extern struct nfsv4_opflag nfsv4_opflag[NFSV4OP_NOPS];
+
+static int nfsv3to4op[NFS_V3NPROCS] = {
+ NFSPROC_NULL,
+ NFSV4OP_GETATTR,
+ NFSV4OP_SETATTR,
+ NFSV4OP_LOOKUP,
+ NFSV4OP_ACCESS,
+ NFSV4OP_READLINK,
+ NFSV4OP_READ,
+ NFSV4OP_WRITE,
+ NFSV4OP_V3CREATE,
+ NFSV4OP_MKDIR,
+ NFSV4OP_SYMLINK,
+ NFSV4OP_MKNOD,
+ NFSV4OP_REMOVE,
+ NFSV4OP_RMDIR,
+ NFSV4OP_RENAME,
+ NFSV4OP_LINK,
+ NFSV4OP_READDIR,
+ NFSV4OP_READDIRPLUS,
+ NFSV4OP_FSSTAT,
+ NFSV4OP_FSINFO,
+ NFSV4OP_PATHCONF,
+ NFSV4OP_COMMIT,
+};
+
+/*
+ * Do an RPC. Basically, get the file handles translated to vnode pointers
+ * and then call the appropriate server routine. The server routines are
+ * split into groups, based on whether they use a file handle or file
+ * handle plus name or ...
+ * The NFS V4 Compound RPC is performed separately by nfsrvd_compound().
+ */
+APPLESTATIC void
+nfsrvd_dorpc(struct nfsrv_descript *nd, int isdgram,
+ NFSPROC_T *p)
+{
+ int error = 0;
+ vnode_t vp;
+ mount_t mp = NULL;
+ struct nfsrvfh fh;
+ struct nfsexstuff nes;
+
+ /*
+ * Get a locked vnode for the first file handle
+ */
+ if (!(nd->nd_flag & ND_NFSV4)) {
+#ifdef DIAGNOSTIC
+ if (nd->nd_repstat)
+ panic("nfsrvd_dorpc");
+#endif
+ /*
+ * For NFSv3, if the malloc/mget allocation is near limits,
+ * return NFSERR_DELAY.
+ */
+ if ((nd->nd_flag & ND_NFSV3) && nfsrv_mallocmget_limit()) {
+ nd->nd_repstat = NFSERR_DELAY;
+ vp = NULL;
+ } else {
+ error = nfsrv_mtofh(nd, &fh);
+ if (error) {
+ if (error != EBADRPC)
+ printf("nfs dorpc err1=%d\n", error);
+ nd->nd_repstat = NFSERR_GARBAGE;
+ return;
+ }
+ nes.nes_vfslocked = 0;
+ if (nd->nd_flag & ND_PUBLOOKUP)
+ nfsd_fhtovp(nd, &nfs_pubfh, &vp, &nes,
+ &mp, nfs_writerpc[nd->nd_procnum], p);
+ else
+ nfsd_fhtovp(nd, &fh, &vp, &nes,
+ &mp, nfs_writerpc[nd->nd_procnum], p);
+ if (nd->nd_repstat == NFSERR_PROGNOTV4)
+ return;
+ }
+ }
+
+ /*
+ * For V2 and 3, set the ND_SAVEREPLY flag for the recent request
+ * cache, as required.
+ * For V4, nfsrvd_compound() does this.
+ */
+ if (!(nd->nd_flag & ND_NFSV4) && nfsrv_nonidempotent[nd->nd_procnum])
+ nd->nd_flag |= ND_SAVEREPLY;
+
+ nfsrvd_rephead(nd);
+ /*
+ * If nd_repstat is non-zero, just fill in the reply status
+ * to complete the RPC reply for V2. Otherwise, you must do
+ * the RPC.
+ */
+ if (nd->nd_repstat && (nd->nd_flag & ND_NFSV2)) {
+ *nd->nd_errp = nfsd_errmap(nd);
+ NFSINCRGLOBAL(newnfsstats.srvrpccnt[nfsv3to4op[nd->nd_procnum]]);
+ if (mp != NULL) {
+ if (nfs_writerpc[nd->nd_procnum])
+ NFS_ENDWRITE(mp);
+ if (nes.nes_vfslocked)
+ nfsvno_unlockvfs(mp);
+ }
+ return;
+ }
+
+ /*
+ * Now the procedure can be performed. For V4, nfsrvd_compound()
+ * works through the sub-rpcs, otherwise just call the procedure.
+ * The procedures are in three groups with different arguments.
+ * The group is indicated by the value in nfs_retfh[].
+ */
+ if (nd->nd_flag & ND_NFSV4) {
+ nfsrvd_compound(nd, isdgram, p);
+ } else {
+ if (nfs_retfh[nd->nd_procnum] == 1) {
+ if (vp)
+ NFSVOPUNLOCK(vp, 0, p);
+ error = (*(nfsrv3_procs1[nd->nd_procnum]))(nd, isdgram,
+ vp, NULL, (fhandle_t *)fh.nfsrvfh_data, p, &nes);
+ } else if (nfs_retfh[nd->nd_procnum] == 2) {
+ error = (*(nfsrv3_procs2[nd->nd_procnum]))(nd, isdgram,
+ vp, NULL, p, &nes, NULL);
+ } else {
+ error = (*(nfsrv3_procs0[nd->nd_procnum]))(nd, isdgram,
+ vp, p, &nes);
+ }
+ if (mp) {
+ if (nfs_writerpc[nd->nd_procnum])
+ NFS_ENDWRITE(mp);
+ if (nes.nes_vfslocked)
+ nfsvno_unlockvfs(mp);
+ }
+ NFSINCRGLOBAL(newnfsstats.srvrpccnt[nfsv3to4op[nd->nd_procnum]]);
+ }
+ if (error) {
+ if (error != EBADRPC)
+ printf("nfs dorpc err2=%d\n", error);
+ nd->nd_repstat = NFSERR_GARBAGE;
+ }
+ *nd->nd_errp = nfsd_errmap(nd);
+
+ /*
+ * Don't cache certain reply status values.
+ */
+ if (nd->nd_repstat && (nd->nd_flag & ND_SAVEREPLY) &&
+ (nd->nd_repstat == NFSERR_GARBAGE ||
+ nd->nd_repstat == NFSERR_BADXDR ||
+ nd->nd_repstat == NFSERR_MOVED ||
+ nd->nd_repstat == NFSERR_DELAY ||
+ nd->nd_repstat == NFSERR_BADSEQID ||
+ nd->nd_repstat == NFSERR_RESOURCE ||
+ nd->nd_repstat == NFSERR_SERVERFAULT ||
+ nd->nd_repstat == NFSERR_STALECLIENTID ||
+ nd->nd_repstat == NFSERR_STALESTATEID ||
+ nd->nd_repstat == NFSERR_OLDSTATEID ||
+ nd->nd_repstat == NFSERR_BADSTATEID ||
+ nd->nd_repstat == NFSERR_GRACE ||
+ nd->nd_repstat == NFSERR_NOGRACE))
+ nd->nd_flag &= ~ND_SAVEREPLY;
+}
+
+/*
+ * Breaks down a compound RPC request and calls the server routines for
+ * the subprocedures.
+ * Some suboperations are performed directly here to simplify file handle<-->
+ * vnode pointer handling.
+ */
+static void
+nfsrvd_compound(struct nfsrv_descript *nd, int isdgram,
+ NFSPROC_T *p)
+{
+ int i, op;
+ u_int32_t *tl;
+ struct nfsclient *clp, *nclp;
+ int numops, taglen = -1, error = 0, igotlock;
+ u_int32_t minorvers, retops = 0, *retopsp = NULL, *repp;
+ u_char tag[NFSV4_SMALLSTR + 1], *tagstr;
+ vnode_t vp, nvp, savevp;
+ struct nfsrvfh fh;
+ mount_t mp, savemp;
+ struct ucred *credanon;
+ struct nfsexstuff nes, vpnes, savevpnes;
+ static u_int64_t compref = 0;
+
+ NFSVNO_EXINIT(&vpnes);
+ NFSVNO_EXINIT(&savevpnes);
+ /*
+ * Put the seq# of the current compound RPC in nfsrv_descript.
+ * (This is used by nfsrv_checkgetattr(), to see if the write
+ * delegation was created by the same compound RPC as the one
+ * with that Getattr in it.)
+ * Don't worry about the 64bit number wrapping around. It ain't
+ * gonna happen before this server gets shut down/rebooted.
+ */
+ nd->nd_compref = compref++;
+
+ /*
+ * Check for and optionally get a lock on the root. This lock means that
+ * no nfsd will be fiddling with the V4 file system and state stuff. It
+ * is required when the V4 root is being changed, the stable storage
+ * restart file is being updated, or callbacks are being done.
+ * When any of the nfsd are processing an NFSv4 compound RPC, they must
+ * either hold a reference count (nfs_usecnt) or the lock. When
+ * nfsrv_unlock() is called to release the lock, it can optionally
+ * also get a reference count, which saves the need for a call to
+ * nfsrv_getref() after nfsrv_unlock().
+ */
+ /*
+ * First, check to see if we need to wait for an update lock.
+ */
+ igotlock = 0;
+ NFSLOCKV4ROOTMUTEX();
+ if (nfsrv_stablefirst.nsf_flags & NFSNSF_NEEDLOCK)
+ igotlock = nfsv4_lock(&nfsv4rootfs_lock, 1, NULL,
+ NFSV4ROOTLOCKMUTEXPTR);
+ else
+ igotlock = nfsv4_lock(&nfsv4rootfs_lock, 0, NULL,
+ NFSV4ROOTLOCKMUTEXPTR);
+ NFSUNLOCKV4ROOTMUTEX();
+ if (igotlock) {
+ NFSLOCKSTATE(); /* to avoid a race with */
+ NFSUNLOCKSTATE(); /* nfsrv_servertimer() */
+ /*
+ * If I got the lock, I can update the stable storage file.
+ * Done when the grace period is over or a client has long
+ * since expired.
+ */
+ nfsrv_stablefirst.nsf_flags &= ~NFSNSF_NEEDLOCK;
+ if ((nfsrv_stablefirst.nsf_flags &
+ (NFSNSF_GRACEOVER | NFSNSF_UPDATEDONE)) == NFSNSF_GRACEOVER)
+ nfsrv_updatestable(p);
+
+ /*
+ * If at least one client has long since expired, search
+ * the client list for them, write a REVOKE record on the
+ * stable storage file and then remove them from the client
+ * list.
+ */
+ if (nfsrv_stablefirst.nsf_flags & NFSNSF_EXPIREDCLIENT) {
+ nfsrv_stablefirst.nsf_flags &= ~NFSNSF_EXPIREDCLIENT;
+ for (i = 0; i < NFSCLIENTHASHSIZE; i++) {
+ LIST_FOREACH_SAFE(clp, &nfsclienthash[i], lc_hash,
+ nclp) {
+ if (clp->lc_flags & LCL_EXPIREIT) {
+ if (!LIST_EMPTY(&clp->lc_open) ||
+ !LIST_EMPTY(&clp->lc_deleg))
+ nfsrv_writestable(clp->lc_id,
+ clp->lc_idlen, NFSNST_REVOKE, p);
+ nfsrv_cleanclient(clp, p);
+ nfsrv_freedeleglist(&clp->lc_deleg);
+ nfsrv_freedeleglist(&clp->lc_olddeleg);
+ LIST_REMOVE(clp, lc_hash);
+ nfsrv_zapclient(clp, p);
+ }
+ }
+ }
+ }
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ } else {
+ /*
+ * If we didn't get the lock, we need to get a refcnt,
+ * which also checks for and waits for the lock.
+ */
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_getref(&nfsv4rootfs_lock, NULL,
+ NFSV4ROOTLOCKMUTEXPTR);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+
+ /*
+ * If flagged, search for open owners that haven't had any opens
+ * for a long time.
+ */
+ if (nfsrv_stablefirst.nsf_flags & NFSNSF_NOOPENS) {
+ nfsrv_throwawayopens(p);
+ }
+
+ savevp = vp = NULL;
+ savevpnes.nes_vfslocked = vpnes.nes_vfslocked = 0;
+ savemp = mp = NULL;
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ taglen = fxdr_unsigned(int, *tl);
+ if (taglen < 0) {
+ error = EBADRPC;
+ goto nfsmout;
+ }
+ if (taglen <= NFSV4_SMALLSTR)
+ tagstr = tag;
+ else
+ tagstr = malloc(taglen + 1, M_TEMP, M_WAITOK);
+ error = nfsrv_mtostr(nd, tagstr, taglen);
+ if (error) {
+ if (taglen > NFSV4_SMALLSTR)
+ free(tagstr, M_TEMP);
+ taglen = -1;
+ goto nfsmout;
+ }
+ (void) nfsm_strtom(nd, tag, taglen);
+ if (taglen > NFSV4_SMALLSTR) {
+ free(tagstr, M_TEMP);
+ }
+ NFSM_BUILD(retopsp, u_int32_t *, NFSX_UNSIGNED);
+ NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ minorvers = fxdr_unsigned(u_int32_t, *tl++);
+ if (minorvers != NFSV4_MINORVERSION)
+ nd->nd_repstat = NFSERR_MINORVERMISMATCH;
+ if (nd->nd_repstat)
+ numops = 0;
+ else
+ numops = fxdr_unsigned(int, *tl);
+ /*
+ * Loop around doing the sub ops.
+ * vp - is an unlocked vnode pointer for the CFH
+ * savevp - is an unlocked vnode pointer for the SAVEDFH
+ * (at some future date, it might turn out to be more appropriate
+ * to keep the file handles instead of vnode pointers?)
+ * savevpnes and vpnes - are the export flags for the above.
+ */
+ for (i = 0; i < numops; i++) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ NFSM_BUILD(repp, u_int32_t *, 2 * NFSX_UNSIGNED);
+ *repp++ = *tl;
+ op = fxdr_unsigned(int, *tl);
+ if (op < NFSV4OP_ACCESS || op >= NFSV4OP_NOPS) {
+ nd->nd_repstat = NFSERR_OPILLEGAL;
+ *repp = nfsd_errmap(nd);
+ retops++;
+ break;
+ }
+
+ /*
+ * Check for a referral on the current FH and, if so, return
+ * NFSERR_MOVED for all ops that allow it, except Getattr.
+ */
+ if (vp != NULL && op != NFSV4OP_GETATTR &&
+ nfsv4root_getreferral(vp, NULL, 0) != NULL &&
+ nfsrv_errmoved(op)) {
+ nd->nd_repstat = NFSERR_MOVED;
+ *repp = nfsd_errmap(nd);
+ retops++;
+ break;
+ }
+
+ nd->nd_procnum = op;
+ /*
+ * If over flood level, reply NFSERR_RESOURCE, if at the first
+ * Op. (Since a client recovery from NFSERR_RESOURCE can get
+ * really nasty for certain Op sequences, I'll play it safe
+ * and only return the error at the beginning.) The cache
+ * will still function over flood level, but uses lots of
+ * mbufs.)
+ * If nfsrv_mallocmget_limit() returns True, the system is near
+ * to its limit for memory that malloc()/mget() can allocate.
+ */
+ if (i == 0 && nd->nd_rp->rc_refcnt == 0 &&
+ (nfsrv_mallocmget_limit() ||
+ nfsrc_tcpsavedreplies > nfsrc_floodlevel)) {
+ if (nfsrc_tcpsavedreplies > nfsrc_floodlevel) {
+ printf("nfsd server cache flooded, try to");
+ printf(" increase nfsrc_floodlevel\n");
+ }
+ nd->nd_repstat = NFSERR_RESOURCE;
+ *repp = nfsd_errmap(nd);
+ if (op == NFSV4OP_SETATTR) {
+ /*
+ * Setattr replies require a bitmap.
+ * even for errors like these.
+ */
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl = 0;
+ }
+ retops++;
+ break;
+ }
+ if (nfsv4_opflag[op].savereply)
+ nd->nd_flag |= ND_SAVEREPLY;
+ NFSINCRGLOBAL(newnfsstats.srvrpccnt[nd->nd_procnum]);
+ switch (op) {
+ case NFSV4OP_PUTFH:
+ error = nfsrv_mtofh(nd, &fh);
+ if (error)
+ goto nfsmout;
+ if (!nd->nd_repstat) {
+ nes.nes_vfslocked = vpnes.nes_vfslocked;
+ nfsd_fhtovp(nd, &fh, &nvp, &nes, &mp,
+ 0, p);
+ }
+ /* For now, allow this for non-export FHs */
+ if (!nd->nd_repstat) {
+ if (vp)
+ vrele(vp);
+ vp = nvp;
+ NFSVOPUNLOCK(vp, 0, p);
+ vpnes = nes;
+ }
+ break;
+ case NFSV4OP_PUTPUBFH:
+ if (nfs_pubfhset) {
+ nes.nes_vfslocked = vpnes.nes_vfslocked;
+ nfsd_fhtovp(nd, &nfs_pubfh, &nvp,
+ &nes, &mp, 0, p);
+ } else {
+ nd->nd_repstat = NFSERR_NOFILEHANDLE;
+ }
+ if (!nd->nd_repstat) {
+ if (vp)
+ vrele(vp);
+ vp = nvp;
+ NFSVOPUNLOCK(vp, 0, p);
+ vpnes = nes;
+ }
+ break;
+ case NFSV4OP_PUTROOTFH:
+ if (nfs_rootfhset) {
+ nes.nes_vfslocked = vpnes.nes_vfslocked;
+ nfsd_fhtovp(nd, &nfs_rootfh, &nvp,
+ &nes, &mp, 0, p);
+ if (!nd->nd_repstat) {
+ if (vp)
+ vrele(vp);
+ vp = nvp;
+ NFSVOPUNLOCK(vp, 0, p);
+ vpnes = nes;
+ }
+ } else if (nfsv4root_vp && nfsv4root_set) {
+ if (vp) {
+ if (vpnes.nes_vfslocked)
+ nfsvno_unlockvfs(mp);
+ vrele(vp);
+ }
+ vp = nfsv4root_vp;
+ VREF(vp);
+ NFSVNO_SETEXRDONLY(&vpnes);
+ vpnes.nes_vfslocked = 0;
+ mp = vnode_mount(vp);
+ } else {
+ nd->nd_repstat = NFSERR_NOFILEHANDLE;
+ }
+ break;
+ case NFSV4OP_SAVEFH:
+ if (vp && NFSVNO_EXPORTED(&vpnes)) {
+ nd->nd_repstat = 0;
+ /* If vp == savevp, a no-op */
+ if (vp != savevp) {
+ if (savevp)
+ vrele(savevp);
+ VREF(vp);
+ savevp = vp;
+ savevpnes = vpnes;
+ savemp = mp;
+ }
+ } else {
+ nd->nd_repstat = NFSERR_NOFILEHANDLE;
+ }
+ break;
+ case NFSV4OP_RESTOREFH:
+ if (savevp) {
+ nd->nd_repstat = 0;
+ /* If vp == savevp, a no-op */
+ if (vp != savevp) {
+ VREF(savevp);
+ if (mp == NULL || savemp == NULL)
+ panic("nfscmpmp");
+ if (!savevpnes.nes_vfslocked &&
+ vpnes.nes_vfslocked) {
+ if (mp == savemp)
+ panic("nfscmp2");
+ nfsvno_unlockvfs(mp);
+ } else if (savevpnes.nes_vfslocked &&
+ !vpnes.nes_vfslocked) {
+ if (mp == savemp)
+ panic("nfscmp3");
+ savevpnes.nes_vfslocked = nfsvno_lockvfs(savemp);
+ }
+ vrele(vp);
+ vp = savevp;
+ vpnes = savevpnes;
+ mp = savemp;
+ }
+ } else {
+ nd->nd_repstat = NFSERR_RESTOREFH;
+ }
+ break;
+ default:
+ /*
+ * Allow a Lookup, Getattr, GetFH, Secinfo on an
+ * non-exported directory if
+ * nfs_rootfhset. Do I need to allow any other Ops?
+ * (You can only have a non-exported vpnes if
+ * nfs_rootfhset is true. See nfsd_fhtovp())
+ * Allow AUTH_SYS to be used for file systems
+ * exported GSS only for certain Ops, to allow
+ * clients to do mounts more easily.
+ */
+ if (nfsv4_opflag[op].needscfh && vp) {
+ if (!NFSVNO_EXPORTED(&vpnes) &&
+ op != NFSV4OP_LOOKUP &&
+ op != NFSV4OP_GETATTR &&
+ op != NFSV4OP_GETFH &&
+ op != NFSV4OP_SECINFO)
+ nd->nd_repstat = NFSERR_NOFILEHANDLE;
+ else if (NFSVNO_EXGSSONLY(&vpnes) &&
+ !(nd->nd_flag & ND_GSS) &&
+ op != NFSV4OP_LOOKUP &&
+ op != NFSV4OP_GETFH &&
+ op != NFSV4OP_GETATTR &&
+ op != NFSV4OP_SECINFO)
+ nd->nd_repstat = NFSERR_WRONGSEC;
+ if (nd->nd_repstat) {
+ if (op == NFSV4OP_SETATTR) {
+ /*
+ * Setattr reply requires a bitmap
+ * even for errors like these.
+ */
+ NFSM_BUILD(tl, u_int32_t *,
+ NFSX_UNSIGNED);
+ *tl = 0;
+ }
+ break;
+ }
+ }
+ if (nfsv4_opflag[op].retfh == 1) {
+ if (!vp) {
+ nd->nd_repstat = NFSERR_NOFILEHANDLE;
+ break;
+ }
+ VREF(vp);
+ if (nfsv4_opflag[op].modifyfs)
+ NFS_STARTWRITE(NULL, &mp);
+ error = (*(nfsrv4_ops1[op]))(nd, isdgram, vp,
+ &nvp, (fhandle_t *)fh.nfsrvfh_data, p, &vpnes);
+ if (!error && !nd->nd_repstat) {
+ if (vfs_statfs(mp)->f_fsid.val[0] !=
+ vfs_statfs(vnode_mount(nvp))->f_fsid.val[0] ||
+ vfs_statfs(mp)->f_fsid.val[1] !=
+ vfs_statfs(vnode_mount(nvp))->f_fsid.val[1]) {
+ if (vfs_statfs(vnode_mount(nvp))->f_fsid.val[0] ==
+ NFSV4ROOT_FSID0 &&
+ vfs_statfs(vnode_mount(nvp))->f_fsid.val[1] ==
+ NFSV4ROOT_FSID1) {
+ if (vpnes.nes_vfslocked) {
+ nfsvno_unlockvfs(mp);
+ vpnes.nes_vfslocked = 0;
+ }
+ NFSVNO_SETEXRDONLY(&vpnes);
+ mp = vnode_mount(nvp);
+ } else {
+ nd->nd_repstat = nfsvno_checkexp(vnode_mount(nvp),
+ nd->nd_nam, &nes, &credanon);
+ if (!nd->nd_repstat)
+ nd->nd_repstat = nfsd_excred(nd,
+ &nes, credanon);
+ if (!nd->nd_repstat) {
+ if (vpnes.nes_vfslocked)
+ nfsvno_unlockvfs(mp);
+ mp = vnode_mount(nvp);
+ vpnes = nes;
+ vpnes.nes_vfslocked =
+ nfsvno_lockvfs(mp);
+ }
+ }
+ }
+ if (!nd->nd_repstat) {
+ vrele(vp);
+ vp = nvp;
+ }
+ }
+ if (nfsv4_opflag[op].modifyfs)
+ NFS_ENDWRITE(mp);
+ } else if (nfsv4_opflag[op].retfh == 2) {
+ if (vp == NULL || savevp == NULL) {
+ nd->nd_repstat = NFSERR_NOFILEHANDLE;
+ break;
+ } else if (mp != savemp) {
+ nd->nd_repstat = NFSERR_XDEV;
+ break;
+ }
+ VREF(vp);
+ VREF(savevp);
+ if (nfsv4_opflag[op].modifyfs)
+ NFS_STARTWRITE(NULL, &mp);
+ NFSVOPLOCK(savevp, LK_EXCLUSIVE | LK_RETRY, p);
+ error = (*(nfsrv4_ops2[op]))(nd, isdgram, savevp,
+ vp, p, &savevpnes, &vpnes);
+ if (nfsv4_opflag[op].modifyfs)
+ NFS_ENDWRITE(mp);
+ } else {
+ if (nfsv4_opflag[op].retfh != 0)
+ panic("nfsrvd_compound");
+ if (nfsv4_opflag[op].needscfh) {
+ if (vp) {
+ VREF(vp);
+ if (nfsv4_opflag[op].modifyfs)
+ NFS_STARTWRITE(NULL, &mp);
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ } else {
+ nd->nd_repstat = NFSERR_NOFILEHANDLE;
+ if (op == NFSV4OP_SETATTR) {
+ /*
+ * Setattr reply requires a bitmap
+ * even for errors like these.
+ */
+ NFSM_BUILD(tl, u_int32_t *,
+ NFSX_UNSIGNED);
+ *tl = 0;
+ }
+ break;
+ }
+ error = (*(nfsrv4_ops0[op]))(nd, isdgram, vp,
+ p, &vpnes);
+ if (nfsv4_opflag[op].modifyfs)
+ NFS_ENDWRITE(mp);
+ } else {
+ error = (*(nfsrv4_ops0[op]))(nd, isdgram,
+ NULL, p, &vpnes);
+ }
+ }
+ };
+ if (error) {
+ if (error == EBADRPC || error == NFSERR_BADXDR) {
+ nd->nd_repstat = NFSERR_BADXDR;
+ } else {
+ nd->nd_repstat = error;
+ printf("nfsv4 comperr0=%d\n", error);
+ }
+ error = 0;
+ }
+ retops++;
+ if (nd->nd_repstat) {
+ *repp = nfsd_errmap(nd);
+ break;
+ } else {
+ *repp = 0; /* NFS4_OK */
+ }
+ }
+nfsmout:
+ if (error) {
+ if (error == EBADRPC || error == NFSERR_BADXDR)
+ nd->nd_repstat = NFSERR_BADXDR;
+ else
+ printf("nfsv4 comperr1=%d\n", error);
+ }
+ if (taglen == -1) {
+ NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
+ *tl++ = 0;
+ *tl = 0;
+ } else {
+ *retopsp = txdr_unsigned(retops);
+ }
+ if (mp && vpnes.nes_vfslocked)
+ nfsvno_unlockvfs(mp);
+ if (vp)
+ vrele(vp);
+ if (savevp)
+ vrele(savevp);
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_relref(&nfsv4rootfs_lock);
+ NFSUNLOCKV4ROOTMUTEX();
+}
diff --git a/sys/fs/nfsserver/nfs_nfsdstate.c b/sys/fs/nfsserver/nfs_nfsdstate.c
new file mode 100644
index 0000000..e312cb1
--- /dev/null
+++ b/sys/fs/nfsserver/nfs_nfsdstate.c
@@ -0,0 +1,4891 @@
+/*-
+ * Copyright (c) 2009 Rick Macklem, University of Guelph
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#ifndef APPLEKEXT
+#include <fs/nfs/nfsport.h>
+
+struct nfsrv_stablefirst nfsrv_stablefirst;
+int nfsrv_issuedelegs = 0;
+int nfsrv_dolocallocks = 1;
+struct nfsv4lock nfsv4rootfs_lock;
+
+extern int newnfs_numnfsd;
+extern struct nfsstats newnfsstats;
+extern int nfsrv_lease;
+extern struct timeval nfsboottime;
+extern u_int32_t newnfs_true, newnfs_false;
+NFSV4ROOTLOCKMUTEX;
+NFSSTATESPINLOCK;
+
+/*
+ * Hash and lru lists for nfs V4.
+ * (Some would put them in the .h file, but I don't like declaring storage
+ * in a .h)
+ */
+struct nfsclienthashhead nfsclienthash[NFSCLIENTHASHSIZE];
+struct nfslockhashhead nfslockhash[NFSLOCKHASHSIZE];
+#endif /* !APPLEKEXT */
+
+static u_int32_t nfsrv_openpluslock = 0, nfsrv_delegatecnt = 0;
+static time_t nfsrvboottime;
+static int nfsrv_writedelegifpos = 1;
+static int nfsrv_returnoldstateid = 0, nfsrv_clients = 0;
+static int nfsrv_clienthighwater = NFSRV_CLIENTHIGHWATER;
+static int nfsrv_nogsscallback = 0;
+
+/* local functions */
+static void nfsrv_dumpaclient(struct nfsclient *clp,
+ struct nfsd_dumpclients *dumpp);
+static void nfsrv_freeopenowner(struct nfsstate *stp, int cansleep,
+ NFSPROC_T *p);
+static int nfsrv_freeopen(struct nfsstate *stp, int *freedlockp,
+ int cansleep, NFSPROC_T *p);
+static int nfsrv_freelockowner(struct nfsstate *stp, int *freedlockp,
+ int cansleep, NFSPROC_T *p);
+static int nfsrv_freeallnfslocks(struct nfsstate *stp, int *freedlockp,
+ int cansleep, NFSPROC_T *p);
+static void nfsrv_freenfslock(struct nfslock *lop);
+static void nfsrv_freenfslockfile(struct nfslockfile *lfp);
+static void nfsrv_freedeleg(struct nfsstate *);
+static int nfsrv_getstate(struct nfsclient *clp, nfsv4stateid_t *stateidp,
+ u_int32_t flags, struct nfsstate **stpp);
+static void nfsrv_getowner(struct nfsstatehead *hp, struct nfsstate *new_stp,
+ struct nfsstate **stpp);
+static int nfsrv_getlockfh(vnode_t vp, u_short flags,
+ struct nfslockfile **new_lfpp, fhandle_t *nfhp, NFSPROC_T *p);
+static int nfsrv_getlockfile(u_short flags,
+ struct nfslockfile **new_lfpp, struct nfslockfile **lfpp, fhandle_t *nfhp);
+static void nfsrv_insertlock(struct nfslock *new_lop,
+ struct nfslock *insert_lop, struct nfsstate *stp, struct nfslockfile *lfp);
+static void nfsrv_updatelock(struct nfsstate *stp, struct nfslock **new_lopp,
+ struct nfslock **other_lopp, struct nfslockfile *lfp);
+static int nfsrv_getipnumber(u_char *cp);
+static int nfsrv_checkrestart(nfsquad_t clientid, u_int32_t flags,
+ nfsv4stateid_t *stateidp, int specialid);
+static int nfsrv_checkgrace(u_int32_t flags);
+static int nfsrv_docallback(struct nfsclient *clp, int procnum,
+ nfsv4stateid_t *stateidp, int trunc, fhandle_t *fhp,
+ struct nfsvattr *nap, nfsattrbit_t *attrbitp, NFSPROC_T *p);
+static u_int32_t nfsrv_nextclientindex(void);
+static u_int32_t nfsrv_nextstateindex(struct nfsclient *clp);
+static void nfsrv_markstable(struct nfsclient *clp);
+static int nfsrv_checkstable(struct nfsclient *clp);
+static int nfsrv_clientconflict(struct nfsclient *clp, int *haslockp, struct
+ vnode *vp, NFSPROC_T *p);
+static int nfsrv_delegconflict(struct nfsstate *stp, int *haslockp,
+ NFSPROC_T *p, vnode_t vp);
+static int nfsrv_cleandeleg(vnode_t vp, struct nfslockfile *lfp,
+ struct nfsclient *clp, int *haslockp, NFSPROC_T *p);
+static int nfsrv_notsamecredname(struct nfsrv_descript *nd,
+ struct nfsclient *clp);
+static time_t nfsrv_leaseexpiry(void);
+static void nfsrv_delaydelegtimeout(struct nfsstate *stp);
+static int nfsrv_checkseqid(struct nfsrv_descript *nd, u_int32_t seqid,
+ struct nfsstate *stp, struct nfsrvcache *op);
+static void nfsrv_locallocks(vnode_t vp, struct nfslockfile *lfp,
+ NFSPROC_T *p);
+static int nfsrv_nootherstate(struct nfsstate *stp);
+
+/*
+ * Scan the client list for a match and either return the current one,
+ * create a new entry or return an error.
+ * If returning a non-error, the clp structure must either be linked into
+ * the client list or free'd.
+ */
+APPLESTATIC int
+nfsrv_setclient(struct nfsrv_descript *nd, struct nfsclient **new_clpp,
+ nfsquad_t *clientidp, nfsquad_t *confirmp, NFSPROC_T *p)
+{
+ struct nfsclient *clp = NULL, *new_clp = *new_clpp;
+ int i;
+ struct nfsstate *stp, *tstp;
+ struct sockaddr_in *sad, *rad;
+ int zapit = 0, gotit, hasstate = 0, igotlock;
+ static u_int64_t confirm_index = 0;
+
+ /*
+ * Check for state resource limit exceeded.
+ */
+ if (nfsrv_openpluslock > NFSRV_V4STATELIMIT)
+ return (NFSERR_RESOURCE);
+
+ if ((nd->nd_flag & ND_GSS) && nfsrv_nogsscallback)
+ /*
+ * Don't do callbacks for AUTH_GSS.
+ * (Since these aren't yet debugged, they might cause the
+ * server to crap out, if they get past the Init call to
+ * the client.)
+ */
+ new_clp->lc_program = 0;
+
+ /* Lock out other nfsd threads */
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_relref(&nfsv4rootfs_lock);
+ do {
+ igotlock = nfsv4_lock(&nfsv4rootfs_lock, 1, NULL,
+ NFSV4ROOTLOCKMUTEXPTR);
+ } while (!igotlock);
+ NFSUNLOCKV4ROOTMUTEX();
+ NFSLOCKSTATE(); /* to avoid a race with */
+ NFSUNLOCKSTATE(); /* nfsrv_servertimer() */
+
+ /*
+ * Search for a match in the client list.
+ */
+ gotit = i = 0;
+ while (i < NFSCLIENTHASHSIZE && !gotit) {
+ LIST_FOREACH(clp, &nfsclienthash[i], lc_hash) {
+ if (new_clp->lc_idlen == clp->lc_idlen &&
+ !NFSBCMP(new_clp->lc_id, clp->lc_id, clp->lc_idlen)) {
+ gotit = 1;
+ break;
+ }
+ }
+ i++;
+ }
+ if (!gotit ||
+ (clp->lc_flags & (LCL_NEEDSCONFIRM | LCL_ADMINREVOKED))) {
+ /*
+ * Get rid of the old one.
+ */
+ if (i != NFSCLIENTHASHSIZE) {
+ LIST_REMOVE(clp, lc_hash);
+ nfsrv_cleanclient(clp, p);
+ nfsrv_freedeleglist(&clp->lc_deleg);
+ nfsrv_freedeleglist(&clp->lc_olddeleg);
+ zapit = 1;
+ }
+ /*
+ * Add it after assigning a client id to it.
+ */
+ new_clp->lc_flags |= LCL_NEEDSCONFIRM;
+ confirmp->qval = new_clp->lc_confirm.qval = ++confirm_index;
+ clientidp->lval[0] = new_clp->lc_clientid.lval[0] =
+ (u_int32_t)nfsrvboottime;
+ clientidp->lval[1] = new_clp->lc_clientid.lval[1] =
+ nfsrv_nextclientindex();
+ new_clp->lc_stateindex = 0;
+ new_clp->lc_statemaxindex = 0;
+ new_clp->lc_cbref = 0;
+ new_clp->lc_expiry = nfsrv_leaseexpiry();
+ LIST_INIT(&new_clp->lc_open);
+ LIST_INIT(&new_clp->lc_deleg);
+ LIST_INIT(&new_clp->lc_olddeleg);
+ for (i = 0; i < NFSSTATEHASHSIZE; i++)
+ LIST_INIT(&new_clp->lc_stateid[i]);
+ LIST_INSERT_HEAD(NFSCLIENTHASH(new_clp->lc_clientid), new_clp,
+ lc_hash);
+ newnfsstats.srvclients++;
+ nfsrv_openpluslock++;
+ nfsrv_clients++;
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ if (zapit)
+ nfsrv_zapclient(clp, p);
+ *new_clpp = NULL;
+ return (0);
+ }
+
+ /*
+ * Now, handle the cases where the id is already issued.
+ */
+ if (nfsrv_notsamecredname(nd, clp)) {
+ /*
+ * Check to see if there is expired state that should go away.
+ */
+ if (clp->lc_expiry < NFSD_MONOSEC &&
+ (!LIST_EMPTY(&clp->lc_open) || !LIST_EMPTY(&clp->lc_deleg))) {
+ nfsrv_cleanclient(clp, p);
+ nfsrv_freedeleglist(&clp->lc_deleg);
+ }
+
+ /*
+ * If there is outstanding state, then reply NFSERR_CLIDINUSE per
+ * RFC3530 Sec. 8.1.2 last para.
+ */
+ if (!LIST_EMPTY(&clp->lc_deleg)) {
+ hasstate = 1;
+ } else if (LIST_EMPTY(&clp->lc_open)) {
+ hasstate = 0;
+ } else {
+ hasstate = 0;
+ /* Look for an Open on the OpenOwner */
+ LIST_FOREACH(stp, &clp->lc_open, ls_list) {
+ if (!LIST_EMPTY(&stp->ls_open)) {
+ hasstate = 1;
+ break;
+ }
+ }
+ }
+ if (hasstate) {
+ /*
+ * If the uid doesn't match, return NFSERR_CLIDINUSE after
+ * filling out the correct ipaddr and portnum.
+ */
+ sad = NFSSOCKADDR(new_clp->lc_req.nr_nam, struct sockaddr_in *);
+ rad = NFSSOCKADDR(clp->lc_req.nr_nam, struct sockaddr_in *);
+ sad->sin_addr.s_addr = rad->sin_addr.s_addr;
+ sad->sin_port = rad->sin_port;
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ return (NFSERR_CLIDINUSE);
+ }
+ }
+
+ if (NFSBCMP(new_clp->lc_verf, clp->lc_verf, NFSX_VERF)) {
+ /*
+ * If the verifier has changed, the client has rebooted
+ * and a new client id is issued. The old state info
+ * can be thrown away once the SETCLIENTID_CONFIRM occurs.
+ */
+ LIST_REMOVE(clp, lc_hash);
+ new_clp->lc_flags |= LCL_NEEDSCONFIRM;
+ confirmp->qval = new_clp->lc_confirm.qval = ++confirm_index;
+ clientidp->lval[0] = new_clp->lc_clientid.lval[0] =
+ nfsrvboottime;
+ clientidp->lval[1] = new_clp->lc_clientid.lval[1] =
+ nfsrv_nextclientindex();
+ new_clp->lc_stateindex = 0;
+ new_clp->lc_statemaxindex = 0;
+ new_clp->lc_cbref = 0;
+ new_clp->lc_expiry = nfsrv_leaseexpiry();
+
+ /*
+ * Save the state until confirmed.
+ */
+ LIST_NEWHEAD(&new_clp->lc_open, &clp->lc_open, ls_list);
+ LIST_FOREACH(tstp, &new_clp->lc_open, ls_list)
+ tstp->ls_clp = new_clp;
+ LIST_NEWHEAD(&new_clp->lc_deleg, &clp->lc_deleg, ls_list);
+ LIST_FOREACH(tstp, &new_clp->lc_deleg, ls_list)
+ tstp->ls_clp = new_clp;
+ LIST_NEWHEAD(&new_clp->lc_olddeleg, &clp->lc_olddeleg,
+ ls_list);
+ LIST_FOREACH(tstp, &new_clp->lc_olddeleg, ls_list)
+ tstp->ls_clp = new_clp;
+ for (i = 0; i < NFSSTATEHASHSIZE; i++) {
+ LIST_NEWHEAD(&new_clp->lc_stateid[i],
+ &clp->lc_stateid[i], ls_hash);
+ LIST_FOREACH(tstp, &new_clp->lc_stateid[i], ls_list)
+ tstp->ls_clp = new_clp;
+ }
+ LIST_INSERT_HEAD(NFSCLIENTHASH(new_clp->lc_clientid), new_clp,
+ lc_hash);
+ newnfsstats.srvclients++;
+ nfsrv_openpluslock++;
+ nfsrv_clients++;
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+
+ /*
+ * Must wait until any outstanding callback on the old clp
+ * completes.
+ */
+ while (clp->lc_cbref) {
+ clp->lc_flags |= LCL_WAKEUPWANTED;
+ (void) tsleep((caddr_t)clp, PZERO - 1,
+ "nfsd clp", 10 * hz);
+ }
+ nfsrv_zapclient(clp, p);
+ *new_clpp = NULL;
+ return (0);
+ }
+ /*
+ * id and verifier match, so update the net address info
+ * and get rid of any existing callback authentication
+ * handle, so a new one will be acquired.
+ */
+ LIST_REMOVE(clp, lc_hash);
+ new_clp->lc_flags |= (LCL_NEEDSCONFIRM | LCL_DONTCLEAN);
+ new_clp->lc_expiry = nfsrv_leaseexpiry();
+ confirmp->qval = new_clp->lc_confirm.qval = ++confirm_index;
+ clientidp->lval[0] = new_clp->lc_clientid.lval[0] =
+ clp->lc_clientid.lval[0];
+ clientidp->lval[1] = new_clp->lc_clientid.lval[1] =
+ clp->lc_clientid.lval[1];
+ new_clp->lc_delegtime = clp->lc_delegtime;
+ new_clp->lc_stateindex = clp->lc_stateindex;
+ new_clp->lc_statemaxindex = clp->lc_statemaxindex;
+ new_clp->lc_cbref = 0;
+ LIST_NEWHEAD(&new_clp->lc_open, &clp->lc_open, ls_list);
+ LIST_FOREACH(tstp, &new_clp->lc_open, ls_list)
+ tstp->ls_clp = new_clp;
+ LIST_NEWHEAD(&new_clp->lc_deleg, &clp->lc_deleg, ls_list);
+ LIST_FOREACH(tstp, &new_clp->lc_deleg, ls_list)
+ tstp->ls_clp = new_clp;
+ LIST_NEWHEAD(&new_clp->lc_olddeleg, &clp->lc_olddeleg, ls_list);
+ LIST_FOREACH(tstp, &new_clp->lc_olddeleg, ls_list)
+ tstp->ls_clp = new_clp;
+ for (i = 0; i < NFSSTATEHASHSIZE; i++) {
+ LIST_NEWHEAD(&new_clp->lc_stateid[i], &clp->lc_stateid[i],
+ ls_hash);
+ LIST_FOREACH(tstp, &new_clp->lc_stateid[i], ls_list)
+ tstp->ls_clp = new_clp;
+ }
+ LIST_INSERT_HEAD(NFSCLIENTHASH(new_clp->lc_clientid), new_clp,
+ lc_hash);
+ newnfsstats.srvclients++;
+ nfsrv_openpluslock++;
+ nfsrv_clients++;
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+
+ /*
+ * Must wait until any outstanding callback on the old clp
+ * completes.
+ */
+ while (clp->lc_cbref) {
+ clp->lc_flags |= LCL_WAKEUPWANTED;
+ (void) tsleep((caddr_t)clp, PZERO - 1, "nfsd clp", 10 * hz);
+ }
+ nfsrv_zapclient(clp, p);
+ *new_clpp = NULL;
+ return (0);
+}
+
+/*
+ * Check to see if the client id exists and optionally confirm it.
+ */
+APPLESTATIC int
+nfsrv_getclient(nfsquad_t clientid, int opflags, struct nfsclient **clpp,
+ nfsquad_t confirm, struct nfsrv_descript *nd, NFSPROC_T *p)
+{
+ struct nfsclient *clp;
+ struct nfsstate *stp;
+ int i;
+ struct nfsclienthashhead *hp;
+ int error = 0, igotlock, doneok;
+
+ if (clpp)
+ *clpp = NULL;
+ if (nfsrvboottime != clientid.lval[0])
+ return (NFSERR_STALECLIENTID);
+
+ /*
+ * If called with opflags == CLOPS_RENEW, the State Lock is
+ * already held. Otherwise, we need to get either that or,
+ * for the case of Confirm, lock out the nfsd threads.
+ */
+ if (opflags & CLOPS_CONFIRM) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_relref(&nfsv4rootfs_lock);
+ do {
+ igotlock = nfsv4_lock(&nfsv4rootfs_lock, 1, NULL,
+ NFSV4ROOTLOCKMUTEXPTR);
+ } while (!igotlock);
+ NFSUNLOCKV4ROOTMUTEX();
+ NFSLOCKSTATE(); /* to avoid a race with */
+ NFSUNLOCKSTATE(); /* nfsrv_servertimer() */
+ } else if (opflags != CLOPS_RENEW) {
+ NFSLOCKSTATE();
+ }
+
+ hp = NFSCLIENTHASH(clientid);
+ LIST_FOREACH(clp, hp, lc_hash) {
+ if (clp->lc_clientid.lval[1] == clientid.lval[1])
+ break;
+ }
+ if (clp == LIST_END(hp)) {
+ if (opflags & CLOPS_CONFIRM)
+ error = NFSERR_STALECLIENTID;
+ else
+ error = NFSERR_EXPIRED;
+ } else if (clp->lc_flags & LCL_ADMINREVOKED) {
+ /*
+ * If marked admin revoked, just return the error.
+ */
+ error = NFSERR_ADMINREVOKED;
+ }
+ if (error) {
+ if (opflags & CLOPS_CONFIRM) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ } else if (opflags != CLOPS_RENEW) {
+ NFSUNLOCKSTATE();
+ }
+ return (error);
+ }
+
+ /*
+ * Perform any operations specified by the opflags.
+ */
+ if (opflags & CLOPS_CONFIRM) {
+ if (clp->lc_confirm.qval != confirm.qval)
+ error = NFSERR_STALECLIENTID;
+ else if (nfsrv_notsamecredname(nd, clp))
+ error = NFSERR_CLIDINUSE;
+
+ if (!error) {
+ if ((clp->lc_flags & (LCL_NEEDSCONFIRM | LCL_DONTCLEAN)) ==
+ LCL_NEEDSCONFIRM) {
+ /*
+ * Hang onto the delegations (as old delegations)
+ * for an Open with CLAIM_DELEGATE_PREV unless in
+ * grace, but get rid of the rest of the state.
+ */
+ nfsrv_cleanclient(clp, p);
+ nfsrv_freedeleglist(&clp->lc_olddeleg);
+ if (nfsrv_checkgrace(0)) {
+ /* In grace, so just delete delegations */
+ nfsrv_freedeleglist(&clp->lc_deleg);
+ } else {
+ LIST_FOREACH(stp, &clp->lc_deleg, ls_list)
+ stp->ls_flags |= NFSLCK_OLDDELEG;
+ clp->lc_delegtime = NFSD_MONOSEC +
+ nfsrv_lease + NFSRV_LEASEDELTA;
+ LIST_NEWHEAD(&clp->lc_olddeleg, &clp->lc_deleg,
+ ls_list);
+ }
+ }
+ clp->lc_flags &= ~(LCL_NEEDSCONFIRM | LCL_DONTCLEAN);
+ if (clp->lc_program)
+ clp->lc_flags |= LCL_NEEDSCBNULL;
+ }
+ } else if (clp->lc_flags & LCL_NEEDSCONFIRM) {
+ error = NFSERR_EXPIRED;
+ }
+
+ /*
+ * If called by the Renew Op, we must check the principal.
+ */
+ if (!error && (opflags & CLOPS_RENEWOP)) {
+ if (nfsrv_notsamecredname(nd, clp)) {
+ doneok = 0;
+ for (i = 0; i < NFSSTATEHASHSIZE && doneok == 0; i++) {
+ LIST_FOREACH(stp, &clp->lc_stateid[i], ls_hash) {
+ if ((stp->ls_flags & NFSLCK_OPEN) &&
+ stp->ls_uid == nd->nd_cred->cr_uid) {
+ doneok = 1;
+ break;
+ }
+ }
+ }
+ if (!doneok)
+ error = NFSERR_ACCES;
+ }
+ if (!error && (clp->lc_flags & LCL_CBDOWN))
+ error = NFSERR_CBPATHDOWN;
+ }
+ if ((!error || error == NFSERR_CBPATHDOWN) &&
+ (opflags & CLOPS_RENEW)) {
+ clp->lc_expiry = nfsrv_leaseexpiry();
+ }
+ if (opflags & CLOPS_CONFIRM) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ } else if (opflags != CLOPS_RENEW) {
+ NFSUNLOCKSTATE();
+ }
+ if (clpp)
+ *clpp = clp;
+ return (error);
+}
+
+/*
+ * Called from the new nfssvc syscall to admin revoke a clientid.
+ * Returns 0 for success, error otherwise.
+ */
+APPLESTATIC int
+nfsrv_adminrevoke(struct nfsd_clid *revokep, NFSPROC_T *p)
+{
+ struct nfsclient *clp = NULL;
+ int i;
+ int gotit, igotlock;
+
+ /*
+ * First, lock out the nfsd so that state won't change while the
+ * revocation record is being written to the stable storage restart
+ * file.
+ */
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_relref(&nfsv4rootfs_lock);
+ do {
+ igotlock = nfsv4_lock(&nfsv4rootfs_lock, 1, NULL,
+ NFSV4ROOTLOCKMUTEXPTR);
+ } while (!igotlock);
+ NFSUNLOCKV4ROOTMUTEX();
+ NFSLOCKSTATE(); /* to avoid a race with */
+ NFSUNLOCKSTATE(); /* nfsrv_servertimer() */
+
+ /*
+ * Search for a match in the client list.
+ */
+ gotit = i = 0;
+ while (i < NFSCLIENTHASHSIZE && !gotit) {
+ LIST_FOREACH(clp, &nfsclienthash[i], lc_hash) {
+ if (revokep->nclid_idlen == clp->lc_idlen &&
+ !NFSBCMP(revokep->nclid_id, clp->lc_id, clp->lc_idlen)) {
+ gotit = 1;
+ break;
+ }
+ }
+ i++;
+ }
+ if (!gotit) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 0);
+ NFSUNLOCKV4ROOTMUTEX();
+ return (EPERM);
+ }
+
+ /*
+ * Now, write out the revocation record
+ */
+ nfsrv_writestable(clp->lc_id, clp->lc_idlen, NFSNST_REVOKE, p);
+
+ /*
+ * and clear out the state, marking the clientid revoked.
+ */
+ clp->lc_flags &= ~LCL_CALLBACKSON;
+ clp->lc_flags |= LCL_ADMINREVOKED;
+ nfsrv_cleanclient(clp, p);
+ nfsrv_freedeleglist(&clp->lc_deleg);
+ nfsrv_freedeleglist(&clp->lc_olddeleg);
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 0);
+ NFSUNLOCKV4ROOTMUTEX();
+ return (0);
+}
+
+/*
+ * Dump out stats for all clients. Called from nfssvc(2), that is used
+ * newnfsstats.
+ */
+APPLESTATIC void
+nfsrv_dumpclients(struct nfsd_dumpclients *dumpp, int maxcnt)
+{
+ struct nfsclient *clp;
+ int i = 0, cnt = 0;
+
+ NFSLOCKSTATE();
+ /*
+ * Rattle through the client lists until done.
+ */
+ while (i < NFSCLIENTHASHSIZE && cnt < maxcnt) {
+ clp = LIST_FIRST(&nfsclienthash[i]);
+ while (clp != LIST_END(&nfsclienthash[i]) && cnt < maxcnt) {
+ nfsrv_dumpaclient(clp, &dumpp[cnt]);
+ cnt++;
+ clp = LIST_NEXT(clp, lc_hash);
+ }
+ i++;
+ }
+ if (cnt < maxcnt)
+ dumpp[cnt].ndcl_clid.nclid_idlen = 0;
+ NFSUNLOCKSTATE();
+}
+
+/*
+ * Dump stats for a client. Must be called with the NFSSTATELOCK and spl'd.
+ */
+static void
+nfsrv_dumpaclient(struct nfsclient *clp, struct nfsd_dumpclients *dumpp)
+{
+ struct nfsstate *stp, *openstp, *lckownstp;
+ struct nfslock *lop;
+ struct sockaddr *sad;
+ struct sockaddr_in *rad;
+ struct sockaddr_in6 *rad6;
+
+ dumpp->ndcl_nopenowners = dumpp->ndcl_nlockowners = 0;
+ dumpp->ndcl_nopens = dumpp->ndcl_nlocks = 0;
+ dumpp->ndcl_ndelegs = dumpp->ndcl_nolddelegs = 0;
+ dumpp->ndcl_flags = clp->lc_flags;
+ dumpp->ndcl_clid.nclid_idlen = clp->lc_idlen;
+ NFSBCOPY(clp->lc_id, dumpp->ndcl_clid.nclid_id, clp->lc_idlen);
+ sad = NFSSOCKADDR(clp->lc_req.nr_nam, struct sockaddr *);
+ dumpp->ndcl_addrfam = sad->sa_family;
+ if (sad->sa_family == AF_INET) {
+ rad = (struct sockaddr_in *)sad;
+ dumpp->ndcl_cbaddr.sin_addr = rad->sin_addr;
+ } else {
+ rad6 = (struct sockaddr_in6 *)sad;
+ dumpp->ndcl_cbaddr.sin6_addr = rad6->sin6_addr;
+ }
+
+ /*
+ * Now, scan the state lists and total up the opens and locks.
+ */
+ LIST_FOREACH(stp, &clp->lc_open, ls_list) {
+ dumpp->ndcl_nopenowners++;
+ LIST_FOREACH(openstp, &stp->ls_open, ls_list) {
+ dumpp->ndcl_nopens++;
+ LIST_FOREACH(lckownstp, &openstp->ls_open, ls_list) {
+ dumpp->ndcl_nlockowners++;
+ LIST_FOREACH(lop, &lckownstp->ls_lock, lo_lckowner) {
+ dumpp->ndcl_nlocks++;
+ }
+ }
+ }
+ }
+
+ /*
+ * and the delegation lists.
+ */
+ LIST_FOREACH(stp, &clp->lc_deleg, ls_list) {
+ dumpp->ndcl_ndelegs++;
+ }
+ LIST_FOREACH(stp, &clp->lc_olddeleg, ls_list) {
+ dumpp->ndcl_nolddelegs++;
+ }
+}
+
+/*
+ * Dump out lock stats for a file.
+ */
+APPLESTATIC void
+nfsrv_dumplocks(vnode_t vp, struct nfsd_dumplocks *ldumpp, int maxcnt,
+ NFSPROC_T *p)
+{
+ struct nfsstate *stp;
+ struct nfslock *lop;
+ int cnt = 0;
+ struct nfslockfile *lfp;
+ struct sockaddr *sad;
+ struct sockaddr_in *rad;
+ struct sockaddr_in6 *rad6;
+ int ret;
+ fhandle_t nfh;
+
+ ret = nfsrv_getlockfh(vp, 0, NULL, &nfh, p);
+ NFSLOCKSTATE();
+ if (!ret)
+ ret = nfsrv_getlockfile(0, NULL, &lfp, &nfh);
+ if (ret) {
+ ldumpp[0].ndlck_clid.nclid_idlen = 0;
+ NFSUNLOCKSTATE();
+ return;
+ }
+
+ /*
+ * For each open share on file, dump it out.
+ */
+ stp = LIST_FIRST(&lfp->lf_open);
+ while (stp != LIST_END(&lfp->lf_open) && cnt < maxcnt) {
+ ldumpp[cnt].ndlck_flags = stp->ls_flags;
+ ldumpp[cnt].ndlck_stateid.seqid = stp->ls_stateid.seqid;
+ ldumpp[cnt].ndlck_stateid.other[0] = stp->ls_stateid.other[0];
+ ldumpp[cnt].ndlck_stateid.other[1] = stp->ls_stateid.other[1];
+ ldumpp[cnt].ndlck_stateid.other[2] = stp->ls_stateid.other[2];
+ ldumpp[cnt].ndlck_owner.nclid_idlen =
+ stp->ls_openowner->ls_ownerlen;
+ NFSBCOPY(stp->ls_openowner->ls_owner,
+ ldumpp[cnt].ndlck_owner.nclid_id,
+ stp->ls_openowner->ls_ownerlen);
+ ldumpp[cnt].ndlck_clid.nclid_idlen = stp->ls_clp->lc_idlen;
+ NFSBCOPY(stp->ls_clp->lc_id, ldumpp[cnt].ndlck_clid.nclid_id,
+ stp->ls_clp->lc_idlen);
+ sad=NFSSOCKADDR(stp->ls_clp->lc_req.nr_nam, struct sockaddr *);
+ ldumpp[cnt].ndlck_addrfam = sad->sa_family;
+ if (sad->sa_family == AF_INET) {
+ rad = (struct sockaddr_in *)sad;
+ ldumpp[cnt].ndlck_cbaddr.sin_addr = rad->sin_addr;
+ } else {
+ rad6 = (struct sockaddr_in6 *)sad;
+ ldumpp[cnt].ndlck_cbaddr.sin6_addr = rad6->sin6_addr;
+ }
+ stp = LIST_NEXT(stp, ls_file);
+ cnt++;
+ }
+
+ /*
+ * and all locks.
+ */
+ lop = LIST_FIRST(&lfp->lf_lock);
+ while (lop != LIST_END(&lfp->lf_lock) && cnt < maxcnt) {
+ stp = lop->lo_stp;
+ ldumpp[cnt].ndlck_flags = lop->lo_flags;
+ ldumpp[cnt].ndlck_first = lop->lo_first;
+ ldumpp[cnt].ndlck_end = lop->lo_end;
+ ldumpp[cnt].ndlck_stateid.seqid = stp->ls_stateid.seqid;
+ ldumpp[cnt].ndlck_stateid.other[0] = stp->ls_stateid.other[0];
+ ldumpp[cnt].ndlck_stateid.other[1] = stp->ls_stateid.other[1];
+ ldumpp[cnt].ndlck_stateid.other[2] = stp->ls_stateid.other[2];
+ ldumpp[cnt].ndlck_owner.nclid_idlen = stp->ls_ownerlen;
+ NFSBCOPY(stp->ls_owner, ldumpp[cnt].ndlck_owner.nclid_id,
+ stp->ls_ownerlen);
+ ldumpp[cnt].ndlck_clid.nclid_idlen = stp->ls_clp->lc_idlen;
+ NFSBCOPY(stp->ls_clp->lc_id, ldumpp[cnt].ndlck_clid.nclid_id,
+ stp->ls_clp->lc_idlen);
+ sad=NFSSOCKADDR(stp->ls_clp->lc_req.nr_nam, struct sockaddr *);
+ ldumpp[cnt].ndlck_addrfam = sad->sa_family;
+ if (sad->sa_family == AF_INET) {
+ rad = (struct sockaddr_in *)sad;
+ ldumpp[cnt].ndlck_cbaddr.sin_addr = rad->sin_addr;
+ } else {
+ rad6 = (struct sockaddr_in6 *)sad;
+ ldumpp[cnt].ndlck_cbaddr.sin6_addr = rad6->sin6_addr;
+ }
+ lop = LIST_NEXT(lop, lo_lckfile);
+ cnt++;
+ }
+
+ /*
+ * and the delegations.
+ */
+ stp = LIST_FIRST(&lfp->lf_deleg);
+ while (stp != LIST_END(&lfp->lf_deleg) && cnt < maxcnt) {
+ ldumpp[cnt].ndlck_flags = stp->ls_flags;
+ ldumpp[cnt].ndlck_stateid.seqid = stp->ls_stateid.seqid;
+ ldumpp[cnt].ndlck_stateid.other[0] = stp->ls_stateid.other[0];
+ ldumpp[cnt].ndlck_stateid.other[1] = stp->ls_stateid.other[1];
+ ldumpp[cnt].ndlck_stateid.other[2] = stp->ls_stateid.other[2];
+ ldumpp[cnt].ndlck_owner.nclid_idlen = 0;
+ ldumpp[cnt].ndlck_clid.nclid_idlen = stp->ls_clp->lc_idlen;
+ NFSBCOPY(stp->ls_clp->lc_id, ldumpp[cnt].ndlck_clid.nclid_id,
+ stp->ls_clp->lc_idlen);
+ sad=NFSSOCKADDR(stp->ls_clp->lc_req.nr_nam, struct sockaddr *);
+ ldumpp[cnt].ndlck_addrfam = sad->sa_family;
+ if (sad->sa_family == AF_INET) {
+ rad = (struct sockaddr_in *)sad;
+ ldumpp[cnt].ndlck_cbaddr.sin_addr = rad->sin_addr;
+ } else {
+ rad6 = (struct sockaddr_in6 *)sad;
+ ldumpp[cnt].ndlck_cbaddr.sin6_addr = rad6->sin6_addr;
+ }
+ stp = LIST_NEXT(stp, ls_file);
+ cnt++;
+ }
+
+ /*
+ * If list isn't full, mark end of list by setting the client name
+ * to zero length.
+ */
+ if (cnt < maxcnt)
+ ldumpp[cnt].ndlck_clid.nclid_idlen = 0;
+ NFSUNLOCKSTATE();
+}
+
+/*
+ * Server timer routine. It can scan any linked list, so long
+ * as it holds the spin lock and there is no exclusive lock on
+ * nfsv4rootfs_lock.
+ * Must be called by a kernel thread and not a timer interrupt,
+ * so that it only runs when the nfsd threads are sleeping on a
+ * uniprocessor and uses the State spin lock for an SMP system.
+ * (For OpenBSD, a kthread is ok. For FreeBSD, I think it is ok
+ * to do this from a callout, since the spin locks work. For
+ * Darwin, I'm not sure what will work correctly yet.)
+ * Should be called once per second.
+ */
+APPLESTATIC void
+nfsrv_servertimer(void)
+{
+ struct nfsclient *clp, *nclp;
+ struct nfsstate *stp, *nstp;
+ int i;
+
+ /*
+ * Make sure nfsboottime is set. This is used by V3 as well
+ * as V4. Note that nfsboottime is not nfsrvboottime, which is
+ * only used by the V4 server for leases.
+ */
+ if (nfsboottime.tv_sec == 0)
+ NFSSETBOOTTIME(nfsboottime);
+
+ /*
+ * If server hasn't started yet, just return.
+ */
+ NFSLOCKSTATE();
+ if (nfsrv_stablefirst.nsf_eograce == 0) {
+ NFSUNLOCKSTATE();
+ return;
+ }
+ if (!(nfsrv_stablefirst.nsf_flags & NFSNSF_UPDATEDONE)) {
+ if (!(nfsrv_stablefirst.nsf_flags & NFSNSF_GRACEOVER) &&
+ NFSD_MONOSEC > nfsrv_stablefirst.nsf_eograce)
+ nfsrv_stablefirst.nsf_flags |=
+ (NFSNSF_GRACEOVER | NFSNSF_NEEDLOCK);
+ NFSUNLOCKSTATE();
+ return;
+ }
+
+ /*
+ * Return now if an nfsd thread has the exclusive lock on
+ * nfsv4rootfs_lock. The dirty trick here is that we have
+ * the spin lock already and the nfsd threads do a:
+ * NFSLOCKSTATE, NFSUNLOCKSTATE after getting the exclusive
+ * lock, so they won't race with code after this check.
+ */
+ if (nfsv4rootfs_lock.nfslock_lock & NFSV4LOCK_LOCK) {
+ NFSUNLOCKSTATE();
+ return;
+ }
+
+ /*
+ * For each client...
+ */
+ for (i = 0; i < NFSCLIENTHASHSIZE; i++) {
+ clp = LIST_FIRST(&nfsclienthash[i]);
+ while (clp != LIST_END(&nfsclienthash[i])) {
+ nclp = LIST_NEXT(clp, lc_hash);
+ if (!(clp->lc_flags & LCL_EXPIREIT)) {
+ if (((clp->lc_expiry + NFSRV_STALELEASE) < NFSD_MONOSEC
+ && ((LIST_EMPTY(&clp->lc_deleg)
+ && LIST_EMPTY(&clp->lc_open)) ||
+ nfsrv_clients > nfsrv_clienthighwater)) ||
+ (clp->lc_expiry + NFSRV_MOULDYLEASE) < NFSD_MONOSEC ||
+ (clp->lc_expiry < NFSD_MONOSEC &&
+ (nfsrv_openpluslock * 10 / 9) > NFSRV_V4STATELIMIT)) {
+ /*
+ * Lease has expired several nfsrv_lease times ago:
+ * PLUS
+ * - no state is associated with it
+ * OR
+ * - above high water mark for number of clients
+ * (nfsrv_clienthighwater should be large enough
+ * that this only occurs when clients fail to
+ * use the same nfs_client_id4.id. Maybe somewhat
+ * higher that the maximum number of clients that
+ * will mount this server?)
+ * OR
+ * Lease has expired a very long time ago
+ * OR
+ * Lease has expired PLUS the number of opens + locks
+ * has exceeded 90% of capacity
+ *
+ * --> Mark for expiry. The actual expiry will be done
+ * by an nfsd sometime soon.
+ */
+ clp->lc_flags |= LCL_EXPIREIT;
+ nfsrv_stablefirst.nsf_flags |=
+ (NFSNSF_NEEDLOCK | NFSNSF_EXPIREDCLIENT);
+ } else {
+ /*
+ * If there are no opens, increment no open tick cnt
+ * If time exceeds NFSNOOPEN, mark it to be thrown away
+ * otherwise, if there is an open, reset no open time
+ * Hopefully, this will avoid excessive re-creation
+ * of open owners and subsequent open confirms.
+ */
+ stp = LIST_FIRST(&clp->lc_open);
+ while (stp != LIST_END(&clp->lc_open)) {
+ nstp = LIST_NEXT(stp, ls_list);
+ if (LIST_EMPTY(&stp->ls_open)) {
+ stp->ls_noopens++;
+ if (stp->ls_noopens > NFSNOOPEN ||
+ (nfsrv_openpluslock * 2) >
+ NFSRV_V4STATELIMIT)
+ nfsrv_stablefirst.nsf_flags |=
+ NFSNSF_NOOPENS;
+ } else {
+ stp->ls_noopens = 0;
+ }
+ stp = nstp;
+ }
+ }
+ }
+ clp = nclp;
+ }
+ }
+ NFSUNLOCKSTATE();
+}
+
+/*
+ * The following set of functions free up the various data structures.
+ */
+/*
+ * Clear out all open/lock state related to this nfsclient.
+ * Caller must hold an exclusive lock on nfsv4rootfs_lock, so that
+ * there are no other active nfsd threads.
+ */
+APPLESTATIC void
+nfsrv_cleanclient(struct nfsclient *clp, NFSPROC_T *p)
+{
+ struct nfsstate *stp, *nstp;
+
+ LIST_FOREACH_SAFE(stp, &clp->lc_open, ls_list, nstp) {
+ nfsrv_freeopenowner(stp, 1, p);
+ }
+}
+
+/*
+ * Free a client that has been cleaned. It should also already have been
+ * removed from the lists.
+ * (Just to be safe w.r.t. newnfs_disconnect(), call this function when
+ * softclock interrupts are enabled.)
+ */
+APPLESTATIC void
+nfsrv_zapclient(struct nfsclient *clp, NFSPROC_T *p)
+{
+
+#ifdef notyet
+ if ((clp->lc_flags & (LCL_GSS | LCL_CALLBACKSON)) ==
+ (LCL_GSS | LCL_CALLBACKSON) &&
+ (clp->lc_hand.nfsh_flag & NFSG_COMPLETE) &&
+ clp->lc_handlelen > 0) {
+ clp->lc_hand.nfsh_flag &= ~NFSG_COMPLETE;
+ clp->lc_hand.nfsh_flag |= NFSG_DESTROYED;
+ (void) nfsrv_docallback(clp, NFSV4PROC_CBNULL,
+ NULL, 0, NULL, NULL, NULL, p);
+ }
+#endif
+ newnfs_disconnect(&clp->lc_req);
+ NFSSOCKADDRFREE(clp->lc_req.nr_nam);
+ NFSFREEMUTEX(&clp->lc_req.nr_mtx);
+ free((caddr_t)clp, M_NFSDCLIENT);
+ NFSLOCKSTATE();
+ newnfsstats.srvclients--;
+ nfsrv_openpluslock--;
+ nfsrv_clients--;
+ NFSUNLOCKSTATE();
+}
+
+/*
+ * Free a list of delegation state structures.
+ * (This function will also free all nfslockfile structures that no
+ * longer have associated state.)
+ */
+APPLESTATIC void
+nfsrv_freedeleglist(struct nfsstatehead *sthp)
+{
+ struct nfsstate *stp, *nstp;
+
+ LIST_FOREACH_SAFE(stp, sthp, ls_list, nstp) {
+ nfsrv_freedeleg(stp);
+ }
+ LIST_INIT(sthp);
+}
+
+/*
+ * Free up a delegation.
+ */
+static void
+nfsrv_freedeleg(struct nfsstate *stp)
+{
+ struct nfslockfile *lfp;
+
+ LIST_REMOVE(stp, ls_hash);
+ LIST_REMOVE(stp, ls_list);
+ LIST_REMOVE(stp, ls_file);
+ lfp = stp->ls_lfp;
+ if (LIST_EMPTY(&lfp->lf_open) &&
+ LIST_EMPTY(&lfp->lf_lock) && LIST_EMPTY(&lfp->lf_deleg))
+ nfsrv_freenfslockfile(lfp);
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ newnfsstats.srvdelegates--;
+ nfsrv_openpluslock--;
+ nfsrv_delegatecnt--;
+}
+
+/*
+ * This function frees an open owner and all associated opens.
+ * Must be called with soft clock interrupts disabled.
+ */
+static void
+nfsrv_freeopenowner(struct nfsstate *stp, int cansleep, NFSPROC_T *p)
+{
+ struct nfsstate *nstp, *tstp;
+
+ LIST_REMOVE(stp, ls_list);
+ /*
+ * Now, free all associated opens.
+ */
+ nstp = LIST_FIRST(&stp->ls_open);
+ while (nstp != LIST_END(&stp->ls_open)) {
+ tstp = nstp;
+ nstp = LIST_NEXT(nstp, ls_list);
+ (void) nfsrv_freeopen(tstp, NULL, cansleep, p);
+ }
+ if (stp->ls_op)
+ nfsrvd_derefcache(stp->ls_op);
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ newnfsstats.srvopenowners--;
+ nfsrv_openpluslock--;
+}
+
+/*
+ * This function frees an open (nfsstate open structure) with all associated
+ * lock_owners and locks. It also frees the nfslockfile structure iff there
+ * are no other opens on the file.
+ * Must be called with soft clock interrupts disabled.
+ * Returns 1 if it free'd the nfslockfile, 0 otherwise.
+ */
+static int
+nfsrv_freeopen(struct nfsstate *stp, int *freedlockp, int cansleep,
+ NFSPROC_T *p)
+{
+ struct nfsstate *nstp, *tstp;
+ struct nfslockfile *lfp;
+ int ret = 0, ret2;
+
+ LIST_REMOVE(stp, ls_hash);
+ LIST_REMOVE(stp, ls_list);
+ LIST_REMOVE(stp, ls_file);
+
+ lfp = stp->ls_lfp;
+ /*
+ * The nfslockfile is freed here if there are no locks
+ * associated with the open.
+ * If there are locks associated with the open, the
+ * nfslockfile structure can be freed via nfsrv_freelockowner().
+ * (That is why the call must be here instead of after the loop.)
+ */
+ if (LIST_EMPTY(&lfp->lf_open) && LIST_EMPTY(&lfp->lf_lock) &&
+ LIST_EMPTY(&lfp->lf_deleg)) {
+ nfsrv_freenfslockfile(lfp);
+ ret = 1;
+ }
+ /*
+ * Now, free all lockowners associated with this open.
+ */
+ nstp = LIST_FIRST(&stp->ls_open);
+ while (nstp != LIST_END(&stp->ls_open)) {
+ tstp = nstp;
+ nstp = LIST_NEXT(nstp, ls_list);
+ ret2 = nfsrv_freelockowner(tstp, freedlockp, cansleep, p);
+ if (ret == 0 && ret2 != 0)
+ ret = ret2;
+ }
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ newnfsstats.srvopens--;
+ nfsrv_openpluslock--;
+ return (ret);
+}
+
+/*
+ * Frees a lockowner and all associated locks.
+ * It also frees the nfslockfile structure, if there are no more
+ * references to it.
+ * Must be called with soft clock interrupts disabled.
+ * Returns 1 if it free'd the nfslockfile structure, 1 otherwise.
+ */
+static int
+nfsrv_freelockowner(struct nfsstate *stp, int *freedlockp, int cansleep,
+ NFSPROC_T *p)
+{
+ int ret;
+
+ LIST_REMOVE(stp, ls_hash);
+ LIST_REMOVE(stp, ls_list);
+ ret = nfsrv_freeallnfslocks(stp, freedlockp, cansleep, p);
+ if (stp->ls_op)
+ nfsrvd_derefcache(stp->ls_op);
+ FREE((caddr_t)stp, M_NFSDSTATE);
+ newnfsstats.srvlockowners--;
+ nfsrv_openpluslock--;
+ return (ret);
+}
+
+/*
+ * Free all the nfs locks on a lockowner.
+ * Returns 1 if it free'd the nfslockfile structure, 0 otherwise.
+ * If any byte range lock is free'd, *freedlockp is set to 1.
+ */
+static int
+nfsrv_freeallnfslocks(struct nfsstate *stp, int *freedlockp, int cansleep,
+ NFSPROC_T *p)
+{
+ struct nfslock *lop, *nlop;
+ struct nfslockfile *lfp = NULL, *olfp = NULL;
+ int ret = 0;
+
+ lop = LIST_FIRST(&stp->ls_lock);
+ while (lop != LIST_END(&stp->ls_lock)) {
+ nlop = LIST_NEXT(lop, lo_lckowner);
+ /*
+ * Since locks off a lockowner are ordered by
+ * file, you should update the local locks when
+ * you hit the next file OR the end of the lock
+ * list. If there are no locks for other owners,
+ * it must be done before the lockowner is discarded.
+ * (All this only applies if cansleep == 1.)
+ */
+ olfp = lfp;
+ lfp = lop->lo_lfp;
+ nfsrv_freenfslock(lop);
+ if (freedlockp)
+ *freedlockp = 1;
+ if (LIST_EMPTY(&lfp->lf_open) && LIST_EMPTY(&lfp->lf_lock) &&
+ LIST_EMPTY(&lfp->lf_deleg)) {
+ if (cansleep)
+ nfsrv_locallocks(NULL, lfp, p);
+ nfsrv_freenfslockfile(lfp);
+ /*
+ * Set the pointer(s) to this lockowner NULL,
+ * to indicate it has been free'd and local
+ * locks discarded already.
+ */
+ if (olfp == lfp)
+ olfp = NULL;
+ lfp = NULL;
+ ret = 1;
+ }
+ if (cansleep && olfp != lfp && olfp != NULL)
+ nfsrv_locallocks(NULL, olfp, p);
+ lop = nlop;
+ }
+ if (cansleep && lfp != NULL)
+ nfsrv_locallocks(NULL, olfp, p);
+ return (ret);
+}
+
+/*
+ * Free an nfslock structure.
+ * Must be called with soft clock interrupts disabled.
+ */
+static void
+nfsrv_freenfslock(struct nfslock *lop)
+{
+
+ LIST_REMOVE(lop, lo_lckfile);
+ LIST_REMOVE(lop, lo_lckowner);
+ FREE((caddr_t)lop, M_NFSDLOCK);
+ newnfsstats.srvlocks--;
+ nfsrv_openpluslock--;
+}
+
+/*
+ * This function frees an nfslockfile structure.
+ * Must be called with soft clock interrupts disabled.
+ */
+static void
+nfsrv_freenfslockfile(struct nfslockfile *lfp)
+{
+
+ LIST_REMOVE(lfp, lf_hash);
+ FREE((caddr_t)lfp, M_NFSDLOCKFILE);
+}
+
+/*
+ * This function looks up an nfsstate structure via stateid.
+ */
+static int
+nfsrv_getstate(struct nfsclient *clp, nfsv4stateid_t *stateidp, __unused u_int32_t flags,
+ struct nfsstate **stpp)
+{
+ struct nfsstate *stp;
+ struct nfsstatehead *hp;
+
+ *stpp = NULL;
+ hp = NFSSTATEHASH(clp, *stateidp);
+ LIST_FOREACH(stp, hp, ls_hash) {
+ if (!NFSBCMP(stp->ls_stateid.other, stateidp->other,
+ NFSX_STATEIDOTHER))
+ break;
+ }
+
+ /*
+ * If no state id in list, return NFSERR_BADSTATEID.
+ */
+ if (stp == LIST_END(hp))
+ return (NFSERR_BADSTATEID);
+ *stpp = stp;
+ return (0);
+}
+
+/*
+ * This function gets an nfsstate structure via owner string.
+ */
+static void
+nfsrv_getowner(struct nfsstatehead *hp, struct nfsstate *new_stp,
+ struct nfsstate **stpp)
+{
+ struct nfsstate *stp;
+
+ *stpp = NULL;
+ LIST_FOREACH(stp, hp, ls_list) {
+ if (new_stp->ls_ownerlen == stp->ls_ownerlen &&
+ !NFSBCMP(new_stp->ls_owner,stp->ls_owner,stp->ls_ownerlen)) {
+ *stpp = stp;
+ return;
+ }
+ }
+}
+
+/*
+ * Lock control function called to update lock status.
+ * Returns 0 upon success, -1 if there is no lock and the flags indicate
+ * that one isn't to be created and an NFSERR_xxx for other errors.
+ * The structures new_stp and new_lop are passed in as pointers that should
+ * be set to NULL if the structure is used and shouldn't be free'd.
+ * For the NFSLCK_TEST and NFSLCK_CHECK cases, the structures are
+ * never used and can safely be allocated on the stack. For all other
+ * cases, *new_stpp and *new_lopp should be malloc'd before the call,
+ * in case they are used.
+ */
+APPLESTATIC int
+nfsrv_lockctrl(vnode_t vp, struct nfsstate **new_stpp,
+ struct nfslock **new_lopp, struct nfslockconflict *cfp,
+ nfsquad_t clientid, nfsv4stateid_t *stateidp, __unused struct nfsexstuff *exp,
+ struct nfsrv_descript *nd, NFSPROC_T *p)
+{
+ struct nfslock *lop;
+ struct nfsstate *new_stp = *new_stpp;
+ struct nfslock *new_lop = *new_lopp;
+ struct nfsstate *tstp, *mystp, *nstp;
+ int specialid = 0;
+ struct nfslockfile *lfp;
+ struct nfslock *other_lop = NULL;
+ struct nfsstate *stp, *lckstp = NULL;
+ struct nfsclient *clp = NULL;
+ u_int32_t bits;
+ int error = 0, haslock = 0, ret;
+ int getlckret, delegation = 0;
+ fhandle_t nfh;
+
+ if (new_stp->ls_flags & (NFSLCK_CHECK | NFSLCK_SETATTR)) {
+ /*
+ * Note the special cases of "all 1s" or "all 0s" stateids and
+ * let reads with all 1s go ahead.
+ */
+ if (new_stp->ls_stateid.seqid == 0x0 &&
+ new_stp->ls_stateid.other[0] == 0x0 &&
+ new_stp->ls_stateid.other[1] == 0x0 &&
+ new_stp->ls_stateid.other[2] == 0x0)
+ specialid = 1;
+ else if (new_stp->ls_stateid.seqid == 0xffffffff &&
+ new_stp->ls_stateid.other[0] == 0xffffffff &&
+ new_stp->ls_stateid.other[1] == 0xffffffff &&
+ new_stp->ls_stateid.other[2] == 0xffffffff)
+ specialid = 2;
+ }
+
+ /*
+ * Check for restart conditions (client and server).
+ */
+ error = nfsrv_checkrestart(clientid, new_stp->ls_flags,
+ &new_stp->ls_stateid, specialid);
+ if (error)
+ return (error);
+
+ /*
+ * Check for state resource limit exceeded.
+ */
+ if ((new_stp->ls_flags & NFSLCK_LOCK) &&
+ nfsrv_openpluslock > NFSRV_V4STATELIMIT)
+ return (NFSERR_RESOURCE);
+
+ /*
+ * For Lock, check for a conflict with a lock held by
+ * a process running locally on the server now, before
+ * monkeying with nfsd state. Since the vp is locked, any
+ * other local calls are blocked during this Op.
+ */
+ if (new_stp->ls_flags & NFSLCK_LOCK) {
+ if (new_lop->lo_flags & NFSLCK_WRITE)
+ error = nfsvno_localconflict(vp, F_WRLCK,
+ new_lop->lo_first, new_lop->lo_end, cfp, p);
+ else
+ error = nfsvno_localconflict(vp, F_RDLCK,
+ new_lop->lo_first, new_lop->lo_end, cfp, p);
+ if (error)
+ return (error);
+ }
+
+ /*
+ * For the lock case, get another nfslock structure,
+ * just in case we need it.
+ * Malloc now, before we start sifting through the linked lists,
+ * in case we have to wait for memory.
+ */
+tryagain:
+ if (new_stp->ls_flags & NFSLCK_LOCK)
+ MALLOC(other_lop, struct nfslock *, sizeof (struct nfslock),
+ M_NFSDLOCK, M_WAITOK);
+
+ /*
+ * Get the lockfile structure for CFH now, so we can do a sanity
+ * check against the stateid, before incrementing the seqid#, since
+ * we want to return NFSERR_BADSTATEID on failure and the seqid#
+ * shouldn't be incremented for this case.
+ * If nfsrv_getlockfile() returns -1, it means "not found", which
+ * will be handled later.
+ */
+ getlckret = nfsrv_getlockfh(vp, new_stp->ls_flags, NULL, &nfh, p);
+ NFSLOCKSTATE();
+ if (!getlckret)
+ getlckret = nfsrv_getlockfile(new_stp->ls_flags, NULL,
+ &lfp, &nfh);
+ if (getlckret != 0 && getlckret != -1) {
+ NFSUNLOCKSTATE();
+ if (other_lop)
+ FREE((caddr_t)other_lop, M_NFSDLOCK);
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (getlckret);
+ }
+
+ /*
+ * Since the code is manipulating lists that are also
+ * manipulated by nfsrv_servertimer(), soft clock interrupts
+ * must be masked off.
+ */
+ if (specialid == 0) {
+ if (new_stp->ls_flags & NFSLCK_TEST) {
+ /*
+ * RFC 3530 does not list LockT as an op that renews a
+ * lease, but the concensus seems to be that it is ok
+ * for a server to do so.
+ */
+ error = nfsrv_getclient(clientid, CLOPS_RENEW, &clp,
+ (nfsquad_t)((u_quad_t)0), NULL, p);
+
+ /*
+ * Since NFSERR_EXPIRED, NFSERR_ADMINREVOKED are not valid
+ * error returns for LockT, just go ahead and test for a lock,
+ * since there are no locks for this client, but other locks
+ * can conflict. (ie. same client will always be false)
+ */
+ if (error == NFSERR_EXPIRED || error == NFSERR_ADMINREVOKED)
+ error = 0;
+ lckstp = new_stp;
+ } else {
+ error = nfsrv_getclient(clientid, CLOPS_RENEW, &clp,
+ (nfsquad_t)((u_quad_t)0), NULL, p);
+ if (error == 0)
+ /*
+ * Look up the stateid
+ */
+ error = nfsrv_getstate(clp, &new_stp->ls_stateid,
+ new_stp->ls_flags, &stp);
+ /*
+ * do some sanity checks for an unconfirmed open or a
+ * stateid that refers to the wrong file, for an open stateid
+ */
+ if (error == 0 && (stp->ls_flags & NFSLCK_OPEN) &&
+ ((stp->ls_openowner->ls_flags & NFSLCK_NEEDSCONFIRM) ||
+ (getlckret != -1 && stp->ls_lfp != lfp)))
+ error = NFSERR_BADSTATEID;
+ if (error == 0 &&
+ (stp->ls_flags & (NFSLCK_DELEGREAD | NFSLCK_DELEGWRITE)) &&
+ getlckret != -1 && stp->ls_lfp != lfp)
+ error = NFSERR_BADSTATEID;
+
+ /*
+ * If the lockowner stateid doesn't refer to the same file,
+ * I believe that is considered ok, since some clients will
+ * only create a single lockowner and use that for all locks
+ * on all files.
+ * For now, log it as a diagnostic, instead of considering it
+ * a BadStateid.
+ */
+ if (error == 0 && (stp->ls_flags &
+ (NFSLCK_OPEN | NFSLCK_DELEGREAD | NFSLCK_DELEGWRITE)) == 0 &&
+ getlckret != -1 && stp->ls_lfp != lfp) {
+#ifdef DIAGNOSTIC
+ printf("Got a lock statid for different file open\n");
+#endif
+ /*
+ error = NFSERR_BADSTATEID;
+ */
+ }
+
+ if (error == 0) {
+ if (new_stp->ls_flags & NFSLCK_OPENTOLOCK) {
+ /*
+ * If haslock set, we've already checked the seqid.
+ */
+ if (!haslock) {
+ if (stp->ls_flags & NFSLCK_OPEN)
+ error = nfsrv_checkseqid(nd, new_stp->ls_seq,
+ stp->ls_openowner, new_stp->ls_op);
+ else
+ error = NFSERR_BADSTATEID;
+ }
+ if (!error)
+ nfsrv_getowner(&stp->ls_open, new_stp, &lckstp);
+ if (lckstp)
+ /*
+ * I believe this should be an error, but it
+ * isn't obvious what NFSERR_xxx would be
+ * appropriate, so I'll use NFSERR_INVAL for now.
+ */
+ error = NFSERR_INVAL;
+ else
+ lckstp = new_stp;
+ } else if (new_stp->ls_flags&(NFSLCK_LOCK|NFSLCK_UNLOCK)) {
+ /*
+ * If haslock set, ditto above.
+ */
+ if (!haslock) {
+ if (stp->ls_flags & NFSLCK_OPEN)
+ error = NFSERR_BADSTATEID;
+ else
+ error = nfsrv_checkseqid(nd, new_stp->ls_seq,
+ stp, new_stp->ls_op);
+ }
+ lckstp = stp;
+ } else {
+ lckstp = stp;
+ }
+ }
+ /*
+ * If the seqid part of the stateid isn't the same, return
+ * NFSERR_OLDSTATEID for cases other than I/O Ops.
+ * For I/O Ops, only return NFSERR_OLDSTATEID if
+ * nfsrv_returnoldstateid is set. (The concensus on the email
+ * list was that most clients would prefer to not receive
+ * NFSERR_OLDSTATEID for I/O Ops, but the RFC suggests that that
+ * is what will happen, so I use the nfsrv_returnoldstateid to
+ * allow for either server configuration.)
+ */
+ if (!error && stp->ls_stateid.seqid!=new_stp->ls_stateid.seqid &&
+ (!(new_stp->ls_flags & NFSLCK_CHECK) ||
+ nfsrv_returnoldstateid))
+ error = NFSERR_OLDSTATEID;
+ }
+ }
+
+ /*
+ * Now we can check for grace.
+ */
+ if (!error)
+ error = nfsrv_checkgrace(new_stp->ls_flags);
+ if ((new_stp->ls_flags & NFSLCK_RECLAIM) && !error &&
+ nfsrv_checkstable(clp))
+ error = NFSERR_NOGRACE;
+ /*
+ * If we successfully Reclaimed state, note that.
+ */
+ if ((new_stp->ls_flags & NFSLCK_RECLAIM) && !error)
+ nfsrv_markstable(clp);
+
+ /*
+ * If nd_repstat is set, we can return that now, since the
+ * seqid# has been incremented.
+ */
+ if (nd->nd_repstat && !error)
+ error = nd->nd_repstat;
+ if (error) {
+ NFSUNLOCKSTATE();
+ if (other_lop)
+ FREE((caddr_t)other_lop, M_NFSDLOCK);
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (error);
+ }
+
+ /*
+ * Check the nfsrv_getlockfile return.
+ * Returned -1 if no structure found.
+ */
+ if (getlckret == -1) {
+ error = NFSERR_EXPIRED;
+ /*
+ * Called from lockt, so no lock is OK.
+ */
+ if (new_stp->ls_flags & NFSLCK_TEST) {
+ error = 0;
+ } else if (new_stp->ls_flags &
+ (NFSLCK_CHECK | NFSLCK_SETATTR)) {
+ /*
+ * Called to check for a lock, OK if the stateid is all
+ * 1s or all 0s, but there should be an nfsstate
+ * otherwise.
+ * (ie. If there is no open, I'll assume no share
+ * deny bits.)
+ */
+ if (specialid)
+ error = 0;
+ else
+ error = NFSERR_BADSTATEID;
+ }
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ /*
+ * Called to lock or unlock, so the lock has gone away.
+ */
+ return (error);
+ }
+
+ /*
+ * For NFSLCK_CHECK and NFSLCK_LOCK, test for a share conflict.
+ * For NFSLCK_CHECK, allow a read if write access is granted,
+ * but check for a deny. For NFSLCK_LOCK, require correct access,
+ * which implies a conflicting deny can't exist.
+ */
+ if (new_stp->ls_flags & (NFSLCK_CHECK | NFSLCK_LOCK)) {
+ /*
+ * Four kinds of state id:
+ * - specialid (all 0s or all 1s), only for NFSLCK_CHECK
+ * - stateid for an open
+ * - stateid for a delegation
+ * - stateid for a lock owner
+ */
+ if (!specialid) {
+ if (stp->ls_flags & (NFSLCK_DELEGREAD | NFSLCK_DELEGWRITE)) {
+ delegation = 1;
+ mystp = stp;
+ nfsrv_delaydelegtimeout(stp);
+ } else if (stp->ls_flags & NFSLCK_OPEN) {
+ mystp = stp;
+ } else {
+ mystp = stp->ls_openstp;
+ }
+ /*
+ * If locking or checking, require correct access
+ * bit set.
+ */
+ if (((new_stp->ls_flags & NFSLCK_LOCK) &&
+ !((new_lop->lo_flags >> NFSLCK_LOCKSHIFT) &
+ mystp->ls_flags & NFSLCK_ACCESSBITS)) ||
+ ((new_stp->ls_flags & (NFSLCK_CHECK|NFSLCK_READACCESS)) ==
+ (NFSLCK_CHECK | NFSLCK_READACCESS) &&
+ !(mystp->ls_flags & NFSLCK_READACCESS)) ||
+ ((new_stp->ls_flags & (NFSLCK_CHECK|NFSLCK_WRITEACCESS)) ==
+ (NFSLCK_CHECK | NFSLCK_WRITEACCESS) &&
+ !(mystp->ls_flags & NFSLCK_WRITEACCESS))) {
+ NFSUNLOCKSTATE();
+ if (other_lop)
+ FREE((caddr_t)other_lop, M_NFSDLOCK);
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (NFSERR_OPENMODE);
+ }
+ } else
+ mystp = NULL;
+ if ((new_stp->ls_flags & NFSLCK_CHECK) && !delegation) {
+ /*
+ * Check for a conflicting deny bit.
+ */
+ LIST_FOREACH(tstp, &lfp->lf_open, ls_file) {
+ if (tstp != mystp) {
+ bits = tstp->ls_flags;
+ bits >>= NFSLCK_SHIFT;
+ if (new_stp->ls_flags & bits & NFSLCK_ACCESSBITS) {
+ ret = nfsrv_clientconflict(tstp->ls_clp, &haslock,
+ vp, p);
+ if (ret) {
+ /*
+ * nfsrv_clientconflict unlocks state
+ * when it returns non-zero.
+ */
+ lckstp = NULL;
+ goto tryagain;
+ }
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (NFSERR_OPENMODE);
+ }
+ }
+ }
+
+ /* We're outta here */
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (0);
+ }
+ }
+
+ /*
+ * For setattr, just get rid of all the Delegations for other clients.
+ */
+ if (new_stp->ls_flags & NFSLCK_SETATTR) {
+ ret = nfsrv_cleandeleg(vp, lfp, clp, &haslock, p);
+ if (ret) {
+ /*
+ * nfsrv_cleandeleg() unlocks state when it
+ * returns non-zero.
+ */
+ if (ret == -1) {
+ lckstp = NULL;
+ goto tryagain;
+ }
+ return (ret);
+ }
+ if (!(new_stp->ls_flags & NFSLCK_CHECK) ||
+ (LIST_EMPTY(&lfp->lf_open) && LIST_EMPTY(&lfp->lf_lock) &&
+ LIST_EMPTY(&lfp->lf_deleg))) {
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (0);
+ }
+ }
+
+ /*
+ * Check for a conflicting delegation. If one is found, call
+ * nfsrv_delegconflict() to handle it. If the v4root lock hasn't
+ * been set yet, it will get the lock. Otherwise, it will recall
+ * the delegation. Then, we try try again...
+ * I currently believe the conflict algorithm to be:
+ * For Lock Ops (Lock/LockT/LockU)
+ * - there is a conflict iff a different client has a write delegation
+ * For Reading (Read Op)
+ * - there is a conflict iff a different client has a write delegation
+ * (the specialids are always a different client)
+ * For Writing (Write/Setattr of size)
+ * - there is a conflict if a different client has any delegation
+ * - there is a conflict if the same client has a read delegation
+ * (I don't understand why this isn't allowed, but that seems to be
+ * the current concensus?)
+ */
+ tstp = LIST_FIRST(&lfp->lf_deleg);
+ while (tstp != LIST_END(&lfp->lf_deleg)) {
+ nstp = LIST_NEXT(tstp, ls_file);
+ if ((((new_stp->ls_flags&(NFSLCK_LOCK|NFSLCK_UNLOCK|NFSLCK_TEST))||
+ ((new_stp->ls_flags & NFSLCK_CHECK) &&
+ (new_lop->lo_flags & NFSLCK_READ))) &&
+ clp != tstp->ls_clp &&
+ (tstp->ls_flags & NFSLCK_DELEGWRITE)) ||
+ ((new_stp->ls_flags & NFSLCK_CHECK) &&
+ (new_lop->lo_flags & NFSLCK_WRITE) &&
+ (clp != tstp->ls_clp ||
+ (tstp->ls_flags & NFSLCK_DELEGREAD)))) {
+ ret = nfsrv_delegconflict(tstp, &haslock, p, vp);
+ if (ret) {
+ /*
+ * nfsrv_delegconflict unlocks state when it
+ * returns non-zero.
+ */
+ if (other_lop) {
+ FREE((caddr_t)other_lop, M_NFSDLOCK);
+ other_lop = NULL;
+ }
+ if (ret == -1) {
+ lckstp = NULL;
+ goto tryagain;
+ }
+ return (ret);
+ }
+ }
+ tstp = nstp;
+ }
+
+ /*
+ * Handle the unlock case by calling nfsrv_updatelock().
+ * (Should I have done some access checking above for unlock? For now,
+ * just let it happen.)
+ */
+ if (new_stp->ls_flags & NFSLCK_UNLOCK) {
+ nfsrv_updatelock(stp, new_lopp, &other_lop, lfp);
+ stateidp->seqid = ++(stp->ls_stateid.seqid);
+ stateidp->other[0] = stp->ls_stateid.other[0];
+ stateidp->other[1] = stp->ls_stateid.other[1];
+ stateidp->other[2] = stp->ls_stateid.other[2];
+ /*
+ * For a non-empty flp->lf_lock list, I believe
+ * nfsrv_locallocks() can safely traverse the list, including
+ * sleeping, for two reasons:
+ * 1 - The Lock/LockU/Close Ops all require a locked
+ * vnode for the file and we currently have that.
+ * 2 - The only other thing that modifies a non-empty
+ * list is nfsrv_cleanclient() and it is always
+ * done with the exclusive nfsv4rootfs_lock held.
+ * Since this Op in progress holds either a shared or
+ * exclusive lock on nfsv4rootfs_lock, that can't
+ * happen now.
+ * However, the structure pointed to by lfp can go
+ * in many places for an empty list, so that is handled
+ * by passing a NULL pointer to nfsrv_locallocks().
+ * Do that check now, while we are still SMP safe.
+ */
+ if (LIST_EMPTY(&lfp->lf_lock))
+ lfp = NULL;
+ NFSUNLOCKSTATE();
+ nfsrv_locallocks(vp, lfp, p);
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (0);
+ }
+
+ /*
+ * Search for a conflicting lock. A lock conflicts if:
+ * - the lock range overlaps and
+ * - at least one lock is a write lock and
+ * - it is not owned by the same lock owner
+ */
+ if (!delegation) {
+ LIST_FOREACH(lop, &lfp->lf_lock, lo_lckfile) {
+ if (new_lop->lo_end > lop->lo_first &&
+ new_lop->lo_first < lop->lo_end &&
+ (new_lop->lo_flags == NFSLCK_WRITE ||
+ lop->lo_flags == NFSLCK_WRITE) &&
+ lckstp != lop->lo_stp &&
+ (lckstp->ls_clp != lop->lo_stp->ls_clp ||
+ lckstp->ls_ownerlen != lop->lo_stp->ls_ownerlen ||
+ NFSBCMP(lckstp->ls_owner, lop->lo_stp->ls_owner,
+ lckstp->ls_ownerlen))) {
+ if (other_lop) {
+ FREE((caddr_t)other_lop, M_NFSDLOCK);
+ other_lop = NULL;
+ }
+ ret = nfsrv_clientconflict(lop->lo_stp->ls_clp,&haslock,vp,p);
+ if (ret) {
+ /*
+ * nfsrv_clientconflict() unlocks state when it
+ * returns non-zero.
+ */
+ lckstp = NULL;
+ goto tryagain;
+ }
+ /*
+ * Found a conflicting lock, so record the conflict and
+ * return the error.
+ */
+ if (cfp) {
+ cfp->cl_clientid.lval[0]=lop->lo_stp->ls_stateid.other[0];
+ cfp->cl_clientid.lval[1]=lop->lo_stp->ls_stateid.other[1];
+ cfp->cl_first = lop->lo_first;
+ cfp->cl_end = lop->lo_end;
+ cfp->cl_flags = lop->lo_flags;
+ cfp->cl_ownerlen = lop->lo_stp->ls_ownerlen;
+ NFSBCOPY(lop->lo_stp->ls_owner, cfp->cl_owner,
+ cfp->cl_ownerlen);
+ }
+ if (new_stp->ls_flags & NFSLCK_RECLAIM)
+ error = NFSERR_RECLAIMCONFLICT;
+ else if (new_stp->ls_flags & NFSLCK_CHECK)
+ error = NFSERR_LOCKED;
+ else
+ error = NFSERR_DENIED;
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (error);
+ }
+ }
+ }
+
+ /*
+ * We only get here if there was no lock that conflicted.
+ */
+ if (new_stp->ls_flags & (NFSLCK_TEST | NFSLCK_CHECK)) {
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (0);
+ }
+
+ /*
+ * We only get here when we are creating or modifying a lock.
+ * There are two variants:
+ * - exist_lock_owner where lock_owner exists
+ * - open_to_lock_owner with new lock_owner
+ */
+ if (!(new_stp->ls_flags & NFSLCK_OPENTOLOCK)) {
+ nfsrv_updatelock(lckstp, new_lopp, &other_lop, lfp);
+ stateidp->seqid = ++(lckstp->ls_stateid.seqid);
+ stateidp->other[0] = lckstp->ls_stateid.other[0];
+ stateidp->other[1] = lckstp->ls_stateid.other[1];
+ stateidp->other[2] = lckstp->ls_stateid.other[2];
+ } else {
+ /*
+ * The new open_to_lock_owner case.
+ * Link the new nfsstate into the lists.
+ */
+ new_stp->ls_seq = new_stp->ls_opentolockseq;
+ nfsrvd_refcache(new_stp->ls_op);
+ stateidp->seqid = new_stp->ls_stateid.seqid = 0;
+ stateidp->other[0] = new_stp->ls_stateid.other[0] =
+ clp->lc_clientid.lval[0];
+ stateidp->other[1] = new_stp->ls_stateid.other[1] =
+ clp->lc_clientid.lval[1];
+ stateidp->other[2] = new_stp->ls_stateid.other[2] =
+ nfsrv_nextstateindex(clp);
+ new_stp->ls_clp = clp;
+ LIST_INIT(&new_stp->ls_lock);
+ new_stp->ls_openstp = stp;
+ new_stp->ls_lfp = lfp;
+ nfsrv_insertlock(new_lop, (struct nfslock *)new_stp, new_stp,
+ lfp);
+ LIST_INSERT_HEAD(NFSSTATEHASH(clp, new_stp->ls_stateid),
+ new_stp, ls_hash);
+ LIST_INSERT_HEAD(&stp->ls_open, new_stp, ls_list);
+ *new_lopp = NULL;
+ *new_stpp = NULL;
+ newnfsstats.srvlockowners++;
+ nfsrv_openpluslock++;
+ }
+ /* See comment above, w.r.t. nfsrv_locallocks(). */
+ if (LIST_EMPTY(&lfp->lf_lock))
+ lfp = NULL;
+ NFSUNLOCKSTATE();
+ nfsrv_locallocks(vp, lfp, p);
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ if (other_lop)
+ FREE((caddr_t)other_lop, M_NFSDLOCK);
+ return (0);
+}
+
+/*
+ * Check for state errors for Open.
+ * repstat is passed back out as an error if more critical errors
+ * are not detected.
+ */
+APPLESTATIC int
+nfsrv_opencheck(nfsquad_t clientid, nfsv4stateid_t *stateidp,
+ struct nfsstate *new_stp, vnode_t vp, struct nfsrv_descript *nd,
+ NFSPROC_T *p, int repstat)
+{
+ struct nfsstate *stp, *nstp;
+ struct nfsclient *clp;
+ struct nfsstate *ownerstp;
+ struct nfslockfile *lfp, *new_lfp;
+ int error, haslock = 0, ret, readonly = 0, getfhret = 0;
+
+ if ((new_stp->ls_flags & NFSLCK_SHAREBITS) == NFSLCK_READACCESS)
+ readonly = 1;
+ /*
+ * Check for restart conditions (client and server).
+ */
+ error = nfsrv_checkrestart(clientid, new_stp->ls_flags,
+ &new_stp->ls_stateid, 0);
+ if (error)
+ return (error);
+
+ /*
+ * Check for state resource limit exceeded.
+ * Technically this should be SMP protected, but the worst
+ * case error is "out by one or two" on the count when it
+ * returns NFSERR_RESOURCE and the limit is just a rather
+ * arbitrary high water mark, so no harm is done.
+ */
+ if (nfsrv_openpluslock > NFSRV_V4STATELIMIT)
+ return (NFSERR_RESOURCE);
+
+tryagain:
+ MALLOC(new_lfp, struct nfslockfile *, sizeof (struct nfslockfile),
+ M_NFSDLOCKFILE, M_WAITOK);
+ if (vp)
+ getfhret = nfsrv_getlockfh(vp, new_stp->ls_flags, &new_lfp,
+ NULL, p);
+ NFSLOCKSTATE();
+ /*
+ * Get the nfsclient structure.
+ * Since the code is manipulating lists that are also
+ * manipulated by nfsrv_servertimer(), soft clock interrupts
+ * must be masked off.
+ */
+ error = nfsrv_getclient(clientid, CLOPS_RENEW, &clp,
+ (nfsquad_t)((u_quad_t)0), NULL, p);
+
+ /*
+ * Look up the open owner. See if it needs confirmation and
+ * check the seq#, as required.
+ */
+ if (!error)
+ nfsrv_getowner(&clp->lc_open, new_stp, &ownerstp);
+
+ if (!error && ownerstp) {
+ error = nfsrv_checkseqid(nd, new_stp->ls_seq, ownerstp,
+ new_stp->ls_op);
+ /*
+ * If the OpenOwner hasn't been confirmed, assume the
+ * old one was a replay and this one is ok.
+ * See: RFC3530 Sec. 14.2.18.
+ */
+ if (error == NFSERR_BADSEQID &&
+ (ownerstp->ls_flags & NFSLCK_NEEDSCONFIRM))
+ error = 0;
+ }
+
+ /*
+ * Check for grace.
+ */
+ if (!error)
+ error = nfsrv_checkgrace(new_stp->ls_flags);
+ if ((new_stp->ls_flags & NFSLCK_RECLAIM) && !error &&
+ nfsrv_checkstable(clp))
+ error = NFSERR_NOGRACE;
+
+ /*
+ * If none of the above errors occurred, let repstat be
+ * returned.
+ */
+ if (repstat && !error)
+ error = repstat;
+ if (error) {
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ free((caddr_t)new_lfp, M_NFSDLOCKFILE);
+ return (error);
+ }
+
+ /*
+ * If vp == NULL, the file doesn't exist yet, so return ok.
+ * (This always happens on the first pass, so haslock must be 0.)
+ */
+ if (vp == NULL) {
+ NFSUNLOCKSTATE();
+ FREE((caddr_t)new_lfp, M_NFSDLOCKFILE);
+ return (0);
+ }
+
+ /*
+ * Get the structure for the underlying file.
+ */
+ if (getfhret)
+ error = getfhret;
+ else
+ error = nfsrv_getlockfile(new_stp->ls_flags, &new_lfp, &lfp,
+ NULL);
+ if (new_lfp)
+ FREE((caddr_t)new_lfp, M_NFSDLOCKFILE);
+ if (error) {
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (error);
+ }
+
+ /*
+ * Search for a conflicting open/share.
+ */
+ if (new_stp->ls_flags & NFSLCK_DELEGCUR) {
+ /*
+ * For Delegate_Cur, search for the matching Delegation,
+ * which indicates no conflict.
+ * An old delegation should have been recovered by the
+ * client doing a Claim_DELEGATE_Prev, so I won't let
+ * it match and return NFSERR_EXPIRED. Should I let it
+ * match?
+ */
+ LIST_FOREACH(stp, &lfp->lf_deleg, ls_file) {
+ if (!(stp->ls_flags & NFSLCK_OLDDELEG) &&
+ stateidp->seqid == stp->ls_stateid.seqid &&
+ !NFSBCMP(stateidp->other, stp->ls_stateid.other,
+ NFSX_STATEIDOTHER))
+ break;
+ }
+ if (stp == LIST_END(&lfp->lf_deleg) ||
+ ((new_stp->ls_flags & NFSLCK_WRITEACCESS) &&
+ (stp->ls_flags & NFSLCK_DELEGREAD))) {
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (NFSERR_EXPIRED);
+ }
+ }
+
+ /*
+ * Check for access/deny bit conflicts. I check for the same
+ * owner as well, in case the client didn't bother.
+ */
+ LIST_FOREACH(stp, &lfp->lf_open, ls_file) {
+ if (!(new_stp->ls_flags & NFSLCK_DELEGCUR) &&
+ (((new_stp->ls_flags & NFSLCK_ACCESSBITS) &
+ ((stp->ls_flags>>NFSLCK_SHIFT) & NFSLCK_ACCESSBITS))||
+ ((stp->ls_flags & NFSLCK_ACCESSBITS) &
+ ((new_stp->ls_flags>>NFSLCK_SHIFT)&NFSLCK_ACCESSBITS)))){
+ ret = nfsrv_clientconflict(stp->ls_clp,&haslock,vp,p);
+ if (ret) {
+ /*
+ * nfsrv_clientconflict() unlocks
+ * state when it returns non-zero.
+ */
+ goto tryagain;
+ }
+ if (new_stp->ls_flags & NFSLCK_RECLAIM)
+ error = NFSERR_RECLAIMCONFLICT;
+ else
+ error = NFSERR_SHAREDENIED;
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (error);
+ }
+ }
+
+ /*
+ * Check for a conflicting delegation. If one is found, call
+ * nfsrv_delegconflict() to handle it. If the v4root lock hasn't
+ * been set yet, it will get the lock. Otherwise, it will recall
+ * the delegation. Then, we try try again...
+ * (If NFSLCK_DELEGCUR is set, it has a delegation, so there
+ * isn't a conflict.)
+ * I currently believe the conflict algorithm to be:
+ * For Open with Read Access and Deny None
+ * - there is a conflict iff a different client has a write delegation
+ * For Open with other Write Access or any Deny except None
+ * - there is a conflict if a different client has any delegation
+ * - there is a conflict if the same client has a read delegation
+ * (The current concensus is that this last case should be
+ * considered a conflict since the client with a read delegation
+ * could have done an Open with ReadAccess and WriteDeny
+ * locally and then not have checked for the WriteDeny.)
+ * Don't check for a Reclaim, since that will be dealt with
+ * by nfsrv_openctrl().
+ */
+ if (!(new_stp->ls_flags &
+ (NFSLCK_DELEGPREV | NFSLCK_DELEGCUR | NFSLCK_RECLAIM))) {
+ stp = LIST_FIRST(&lfp->lf_deleg);
+ while (stp != LIST_END(&lfp->lf_deleg)) {
+ nstp = LIST_NEXT(stp, ls_file);
+ if ((readonly && stp->ls_clp != clp &&
+ (stp->ls_flags & NFSLCK_DELEGWRITE)) ||
+ (!readonly && (stp->ls_clp != clp ||
+ (stp->ls_flags & NFSLCK_DELEGREAD)))) {
+ ret = nfsrv_delegconflict(stp, &haslock, p, vp);
+ if (ret) {
+ /*
+ * nfsrv_delegconflict() unlocks state
+ * when it returns non-zero.
+ */
+ if (ret == -1)
+ goto tryagain;
+ return (ret);
+ }
+ }
+ stp = nstp;
+ }
+ }
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (0);
+}
+
+/*
+ * Open control function to create/update open state for an open.
+ */
+APPLESTATIC int
+nfsrv_openctrl(struct nfsrv_descript *nd, vnode_t vp,
+ struct nfsstate **new_stpp, nfsquad_t clientid, nfsv4stateid_t *stateidp,
+ nfsv4stateid_t *delegstateidp, u_int32_t *rflagsp, struct nfsexstuff *exp,
+ NFSPROC_T *p, u_quad_t filerev)
+{
+ struct nfsstate *new_stp = *new_stpp;
+ struct nfsstate *stp, *nstp;
+ struct nfsstate *openstp = NULL, *new_open, *ownerstp, *new_deleg;
+ struct nfslockfile *lfp, *new_lfp;
+ struct nfsclient *clp;
+ int error, haslock = 0, ret, delegate = 1, writedeleg = 1;
+ int readonly = 0, cbret = 1, getfhret = 0;
+
+ if ((new_stp->ls_flags & NFSLCK_SHAREBITS) == NFSLCK_READACCESS)
+ readonly = 1;
+ /*
+ * Check for restart conditions (client and server).
+ * (Paranoia, should have been detected by nfsrv_opencheck().)
+ * If an error does show up, return NFSERR_EXPIRED, since the
+ * the seqid# has already been incremented.
+ */
+ error = nfsrv_checkrestart(clientid, new_stp->ls_flags,
+ &new_stp->ls_stateid, 0);
+ if (error) {
+ printf("Nfsd: openctrl unexpected restart err=%d\n",
+ error);
+ return (NFSERR_EXPIRED);
+ }
+
+tryagain:
+ MALLOC(new_lfp, struct nfslockfile *, sizeof (struct nfslockfile),
+ M_NFSDLOCKFILE, M_WAITOK);
+ MALLOC(new_open, struct nfsstate *, sizeof (struct nfsstate),
+ M_NFSDSTATE, M_WAITOK);
+ MALLOC(new_deleg, struct nfsstate *, sizeof (struct nfsstate),
+ M_NFSDSTATE, M_WAITOK);
+ getfhret = nfsrv_getlockfh(vp, new_stp->ls_flags, &new_lfp,
+ NULL, p);
+ NFSLOCKSTATE();
+ /*
+ * Get the client structure. Since the linked lists could be changed
+ * by other nfsd processes if this process does a tsleep(), one of
+ * two things must be done.
+ * 1 - don't tsleep()
+ * or
+ * 2 - get the nfsv4_lock() { indicated by haslock == 1 }
+ * before using the lists, since this lock stops the other
+ * nfsd. This should only be used for rare cases, since it
+ * essentially single threads the nfsd.
+ * At this time, it is only done for cases where the stable
+ * storage file must be written prior to completion of state
+ * expiration.
+ */
+ error = nfsrv_getclient(clientid, CLOPS_RENEW, &clp,
+ (nfsquad_t)((u_quad_t)0), NULL, p);
+ if (!error && (clp->lc_flags & LCL_NEEDSCBNULL) &&
+ clp->lc_program) {
+ /*
+ * This happens on the first open for a client
+ * that supports callbacks.
+ */
+ NFSUNLOCKSTATE();
+ /*
+ * Although nfsrv_docallback() will sleep, clp won't
+ * go away, since they are only removed when the
+ * nfsv4_lock() has blocked the nfsd threads. The
+ * fields in clp can change, but having multiple
+ * threads do this Null callback RPC should be
+ * harmless.
+ */
+ cbret = nfsrv_docallback(clp, NFSV4PROC_CBNULL,
+ NULL, 0, NULL, NULL, NULL, p);
+ NFSLOCKSTATE();
+ clp->lc_flags &= ~LCL_NEEDSCBNULL;
+ if (!cbret)
+ clp->lc_flags |= LCL_CALLBACKSON;
+ }
+
+ /*
+ * Look up the open owner. See if it needs confirmation and
+ * check the seq#, as required.
+ */
+ if (!error)
+ nfsrv_getowner(&clp->lc_open, new_stp, &ownerstp);
+
+ if (error) {
+ NFSUNLOCKSTATE();
+ printf("Nfsd: openctrl unexpected state err=%d\n",
+ error);
+ free((caddr_t)new_lfp, M_NFSDLOCKFILE);
+ free((caddr_t)new_open, M_NFSDSTATE);
+ free((caddr_t)new_deleg, M_NFSDSTATE);
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (NFSERR_EXPIRED);
+ }
+
+ if (new_stp->ls_flags & NFSLCK_RECLAIM)
+ nfsrv_markstable(clp);
+
+ /*
+ * Get the structure for the underlying file.
+ */
+ if (getfhret)
+ error = getfhret;
+ else
+ error = nfsrv_getlockfile(new_stp->ls_flags, &new_lfp, &lfp,
+ NULL);
+ if (new_lfp)
+ FREE((caddr_t)new_lfp, M_NFSDLOCKFILE);
+ if (error) {
+ NFSUNLOCKSTATE();
+ printf("Nfsd openctrl unexpected getlockfile err=%d\n",
+ error);
+ free((caddr_t)new_open, M_NFSDSTATE);
+ free((caddr_t)new_deleg, M_NFSDSTATE);
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (error);
+ }
+
+ /*
+ * Search for a conflicting open/share.
+ */
+ if (new_stp->ls_flags & NFSLCK_DELEGCUR) {
+ /*
+ * For Delegate_Cur, search for the matching Delegation,
+ * which indicates no conflict.
+ * An old delegation should have been recovered by the
+ * client doing a Claim_DELEGATE_Prev, so I won't let
+ * it match and return NFSERR_EXPIRED. Should I let it
+ * match?
+ */
+ LIST_FOREACH(stp, &lfp->lf_deleg, ls_file) {
+ if (!(stp->ls_flags & NFSLCK_OLDDELEG) &&
+ stateidp->seqid == stp->ls_stateid.seqid &&
+ !NFSBCMP(stateidp->other, stp->ls_stateid.other,
+ NFSX_STATEIDOTHER))
+ break;
+ }
+ if (stp == LIST_END(&lfp->lf_deleg) ||
+ ((new_stp->ls_flags & NFSLCK_WRITEACCESS) &&
+ (stp->ls_flags & NFSLCK_DELEGREAD))) {
+ NFSUNLOCKSTATE();
+ printf("Nfsd openctrl unexpected expiry\n");
+ free((caddr_t)new_open, M_NFSDSTATE);
+ free((caddr_t)new_deleg, M_NFSDSTATE);
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (NFSERR_EXPIRED);
+ }
+
+ /*
+ * Don't issue a Delegation, since one already exists and
+ * delay delegation timeout, as required.
+ */
+ delegate = 0;
+ nfsrv_delaydelegtimeout(stp);
+ }
+
+ /*
+ * Check for access/deny bit conflicts. I also check for the
+ * same owner, since the client might not have bothered to check.
+ * Also, note an open for the same file and owner, if found,
+ * which is all we do here for Delegate_Cur, since conflict
+ * checking is already done.
+ */
+ LIST_FOREACH(stp, &lfp->lf_open, ls_file) {
+ if (ownerstp && stp->ls_openowner == ownerstp)
+ openstp = stp;
+ if (!(new_stp->ls_flags & NFSLCK_DELEGCUR)) {
+ /*
+ * If another client has the file open, the only
+ * delegation that can be issued is a Read delegation
+ * and only if it is a Read open with Deny none.
+ */
+ if (clp != stp->ls_clp) {
+ if ((stp->ls_flags & NFSLCK_SHAREBITS) ==
+ NFSLCK_READACCESS)
+ writedeleg = 0;
+ else
+ delegate = 0;
+ }
+ if(((new_stp->ls_flags & NFSLCK_ACCESSBITS) &
+ ((stp->ls_flags>>NFSLCK_SHIFT) & NFSLCK_ACCESSBITS))||
+ ((stp->ls_flags & NFSLCK_ACCESSBITS) &
+ ((new_stp->ls_flags>>NFSLCK_SHIFT)&NFSLCK_ACCESSBITS))){
+ ret = nfsrv_clientconflict(stp->ls_clp,&haslock,vp,p);
+ if (ret) {
+ /*
+ * nfsrv_clientconflict() unlocks state
+ * when it returns non-zero.
+ */
+ free((caddr_t)new_open, M_NFSDSTATE);
+ free((caddr_t)new_deleg, M_NFSDSTATE);
+ openstp = NULL;
+ goto tryagain;
+ }
+ if (new_stp->ls_flags & NFSLCK_RECLAIM)
+ error = NFSERR_RECLAIMCONFLICT;
+ else
+ error = NFSERR_SHAREDENIED;
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ free((caddr_t)new_open, M_NFSDSTATE);
+ free((caddr_t)new_deleg, M_NFSDSTATE);
+ printf("nfsd openctrl unexpected client cnfl\n");
+ return (error);
+ }
+ }
+ }
+
+ /*
+ * Check for a conflicting delegation. If one is found, call
+ * nfsrv_delegconflict() to handle it. If the v4root lock hasn't
+ * been set yet, it will get the lock. Otherwise, it will recall
+ * the delegation. Then, we try try again...
+ * (If NFSLCK_DELEGCUR is set, it has a delegation, so there
+ * isn't a conflict.)
+ * I currently believe the conflict algorithm to be:
+ * For Open with Read Access and Deny None
+ * - there is a conflict iff a different client has a write delegation
+ * For Open with other Write Access or any Deny except None
+ * - there is a conflict if a different client has any delegation
+ * - there is a conflict if the same client has a read delegation
+ * (The current concensus is that this last case should be
+ * considered a conflict since the client with a read delegation
+ * could have done an Open with ReadAccess and WriteDeny
+ * locally and then not have checked for the WriteDeny.)
+ */
+ if (!(new_stp->ls_flags & (NFSLCK_DELEGPREV | NFSLCK_DELEGCUR))) {
+ stp = LIST_FIRST(&lfp->lf_deleg);
+ while (stp != LIST_END(&lfp->lf_deleg)) {
+ nstp = LIST_NEXT(stp, ls_file);
+ if (stp->ls_clp != clp && (stp->ls_flags & NFSLCK_DELEGREAD))
+ writedeleg = 0;
+ else
+ delegate = 0;
+ if ((readonly && stp->ls_clp != clp &&
+ (stp->ls_flags & NFSLCK_DELEGWRITE)) ||
+ (!readonly && (stp->ls_clp != clp ||
+ (stp->ls_flags & NFSLCK_DELEGREAD)))) {
+ if (new_stp->ls_flags & NFSLCK_RECLAIM) {
+ delegate = 2;
+ } else {
+ ret = nfsrv_delegconflict(stp, &haslock, p, vp);
+ if (ret) {
+ /*
+ * nfsrv_delegconflict() unlocks state
+ * when it returns non-zero.
+ */
+ printf("Nfsd openctrl unexpected deleg cnfl\n");
+ free((caddr_t)new_open, M_NFSDSTATE);
+ free((caddr_t)new_deleg, M_NFSDSTATE);
+ if (ret == -1) {
+ openstp = NULL;
+ goto tryagain;
+ }
+ return (ret);
+ }
+ }
+ }
+ stp = nstp;
+ }
+ }
+
+ /*
+ * We only get here if there was no open that conflicted.
+ * If an open for the owner exists, or in the access/deny bits.
+ * Otherwise it is a new open. If the open_owner hasn't been
+ * confirmed, replace the open with the new one needing confirmation,
+ * otherwise add the open.
+ */
+ if (new_stp->ls_flags & NFSLCK_DELEGPREV) {
+ /*
+ * Handle NFSLCK_DELEGPREV by searching the old delegations for
+ * a match. If found, just move the old delegation to the current
+ * delegation list and issue open. If not found, return
+ * NFSERR_EXPIRED.
+ */
+ LIST_FOREACH(stp, &clp->lc_olddeleg, ls_list) {
+ if (stp->ls_lfp == lfp) {
+ /* Found it */
+ if (stp->ls_clp != clp)
+ panic("olddeleg clp");
+ LIST_REMOVE(stp, ls_list);
+ LIST_REMOVE(stp, ls_hash);
+ stp->ls_flags &= ~NFSLCK_OLDDELEG;
+ stp->ls_stateid.seqid = delegstateidp->seqid = 0;
+ stp->ls_stateid.other[0] = delegstateidp->other[0] =
+ clp->lc_clientid.lval[0];
+ stp->ls_stateid.other[1] = delegstateidp->other[1] =
+ clp->lc_clientid.lval[1];
+ stp->ls_stateid.other[2] = delegstateidp->other[2] =
+ nfsrv_nextstateindex(clp);
+ stp->ls_compref = nd->nd_compref;
+ LIST_INSERT_HEAD(&clp->lc_deleg, stp, ls_list);
+ LIST_INSERT_HEAD(NFSSTATEHASH(clp,
+ stp->ls_stateid), stp, ls_hash);
+ if (stp->ls_flags & NFSLCK_DELEGWRITE)
+ *rflagsp |= NFSV4OPEN_WRITEDELEGATE;
+ else
+ *rflagsp |= NFSV4OPEN_READDELEGATE;
+ clp->lc_delegtime = NFSD_MONOSEC +
+ nfsrv_lease + NFSRV_LEASEDELTA;
+
+ /*
+ * Now, do the associated open.
+ */
+ new_open->ls_stateid.seqid = 0;
+ new_open->ls_stateid.other[0] = clp->lc_clientid.lval[0];
+ new_open->ls_stateid.other[1] = clp->lc_clientid.lval[1];
+ new_open->ls_stateid.other[2] = nfsrv_nextstateindex(clp);
+ new_open->ls_flags = (new_stp->ls_flags&NFSLCK_DENYBITS)|
+ NFSLCK_OPEN;
+ if (stp->ls_flags & NFSLCK_DELEGWRITE)
+ new_open->ls_flags |= (NFSLCK_READACCESS |
+ NFSLCK_WRITEACCESS);
+ else
+ new_open->ls_flags |= NFSLCK_READACCESS;
+ new_open->ls_uid = new_stp->ls_uid;
+ new_open->ls_lfp = lfp;
+ new_open->ls_clp = clp;
+ LIST_INIT(&new_open->ls_open);
+ LIST_INSERT_HEAD(&lfp->lf_open, new_open, ls_file);
+ LIST_INSERT_HEAD(NFSSTATEHASH(clp, new_open->ls_stateid),
+ new_open, ls_hash);
+ /*
+ * and handle the open owner
+ */
+ if (ownerstp) {
+ new_open->ls_openowner = ownerstp;
+ LIST_INSERT_HEAD(&ownerstp->ls_open,new_open,ls_list);
+ } else {
+ new_open->ls_openowner = new_stp;
+ new_stp->ls_flags = 0;
+ nfsrvd_refcache(new_stp->ls_op);
+ new_stp->ls_noopens = 0;
+ LIST_INIT(&new_stp->ls_open);
+ LIST_INSERT_HEAD(&new_stp->ls_open, new_open, ls_list);
+ LIST_INSERT_HEAD(&clp->lc_open, new_stp, ls_list);
+ *new_stpp = NULL;
+ newnfsstats.srvopenowners++;
+ nfsrv_openpluslock++;
+ }
+ openstp = new_open;
+ new_open = NULL;
+ newnfsstats.srvopens++;
+ nfsrv_openpluslock++;
+ break;
+ }
+ }
+ if (stp == LIST_END(&clp->lc_olddeleg))
+ error = NFSERR_EXPIRED;
+ } else if (new_stp->ls_flags & (NFSLCK_DELEGREAD | NFSLCK_DELEGWRITE)) {
+ /*
+ * Scan to see that no delegation for this client and file
+ * doesn't already exist.
+ * There also shouldn't yet be an Open for this file and
+ * openowner.
+ */
+ LIST_FOREACH(stp, &lfp->lf_deleg, ls_file) {
+ if (stp->ls_clp == clp)
+ break;
+ }
+ if (stp == LIST_END(&lfp->lf_deleg) && openstp == NULL) {
+ /*
+ * This is the Claim_Previous case with a delegation
+ * type != Delegate_None.
+ */
+ /*
+ * First, add the delegation. (Although we must issue the
+ * delegation, we can also ask for an immediate return.)
+ */
+ new_deleg->ls_stateid.seqid = delegstateidp->seqid = 0;
+ new_deleg->ls_stateid.other[0] = delegstateidp->other[0] =
+ clp->lc_clientid.lval[0];
+ new_deleg->ls_stateid.other[1] = delegstateidp->other[1] =
+ clp->lc_clientid.lval[1];
+ new_deleg->ls_stateid.other[2] = delegstateidp->other[2] =
+ nfsrv_nextstateindex(clp);
+ if (new_stp->ls_flags & NFSLCK_DELEGWRITE) {
+ new_deleg->ls_flags = (NFSLCK_DELEGWRITE |
+ NFSLCK_READACCESS | NFSLCK_WRITEACCESS);
+ *rflagsp |= NFSV4OPEN_WRITEDELEGATE;
+ } else {
+ new_deleg->ls_flags = (NFSLCK_DELEGREAD |
+ NFSLCK_READACCESS);
+ *rflagsp |= NFSV4OPEN_READDELEGATE;
+ }
+ new_deleg->ls_uid = new_stp->ls_uid;
+ new_deleg->ls_lfp = lfp;
+ new_deleg->ls_clp = clp;
+ new_deleg->ls_filerev = filerev;
+ new_deleg->ls_compref = nd->nd_compref;
+ LIST_INSERT_HEAD(&lfp->lf_deleg, new_deleg, ls_file);
+ LIST_INSERT_HEAD(NFSSTATEHASH(clp,
+ new_deleg->ls_stateid), new_deleg, ls_hash);
+ LIST_INSERT_HEAD(&clp->lc_deleg, new_deleg, ls_list);
+ new_deleg = NULL;
+ if (delegate == 2 || nfsrv_issuedelegs == 0 ||
+ (clp->lc_flags & (LCL_CALLBACKSON | LCL_CBDOWN)) !=
+ LCL_CALLBACKSON ||
+ NFSRV_V4DELEGLIMIT(nfsrv_delegatecnt) ||
+ !NFSVNO_DELEGOK(vp))
+ *rflagsp |= NFSV4OPEN_RECALL;
+ newnfsstats.srvdelegates++;
+ nfsrv_openpluslock++;
+ nfsrv_delegatecnt++;
+
+ /*
+ * Now, do the associated open.
+ */
+ new_open->ls_stateid.seqid = 0;
+ new_open->ls_stateid.other[0] = clp->lc_clientid.lval[0];
+ new_open->ls_stateid.other[1] = clp->lc_clientid.lval[1];
+ new_open->ls_stateid.other[2] = nfsrv_nextstateindex(clp);
+ new_open->ls_flags = (new_stp->ls_flags & NFSLCK_DENYBITS) |
+ NFSLCK_OPEN;
+ if (new_stp->ls_flags & NFSLCK_DELEGWRITE)
+ new_open->ls_flags |= (NFSLCK_READACCESS |
+ NFSLCK_WRITEACCESS);
+ else
+ new_open->ls_flags |= NFSLCK_READACCESS;
+ new_open->ls_uid = new_stp->ls_uid;
+ new_open->ls_lfp = lfp;
+ new_open->ls_clp = clp;
+ LIST_INIT(&new_open->ls_open);
+ LIST_INSERT_HEAD(&lfp->lf_open, new_open, ls_file);
+ LIST_INSERT_HEAD(NFSSTATEHASH(clp, new_open->ls_stateid),
+ new_open, ls_hash);
+ /*
+ * and handle the open owner
+ */
+ if (ownerstp) {
+ new_open->ls_openowner = ownerstp;
+ LIST_INSERT_HEAD(&ownerstp->ls_open, new_open, ls_list);
+ } else {
+ new_open->ls_openowner = new_stp;
+ new_stp->ls_flags = 0;
+ nfsrvd_refcache(new_stp->ls_op);
+ new_stp->ls_noopens = 0;
+ LIST_INIT(&new_stp->ls_open);
+ LIST_INSERT_HEAD(&new_stp->ls_open, new_open, ls_list);
+ LIST_INSERT_HEAD(&clp->lc_open, new_stp, ls_list);
+ *new_stpp = NULL;
+ newnfsstats.srvopenowners++;
+ nfsrv_openpluslock++;
+ }
+ openstp = new_open;
+ new_open = NULL;
+ newnfsstats.srvopens++;
+ nfsrv_openpluslock++;
+ } else {
+ error = NFSERR_RECLAIMCONFLICT;
+ }
+ } else if (ownerstp) {
+ if (ownerstp->ls_flags & NFSLCK_NEEDSCONFIRM) {
+ /* Replace the open */
+ if (ownerstp->ls_op)
+ nfsrvd_derefcache(ownerstp->ls_op);
+ ownerstp->ls_op = new_stp->ls_op;
+ nfsrvd_refcache(ownerstp->ls_op);
+ ownerstp->ls_seq = new_stp->ls_seq;
+ *rflagsp |= NFSV4OPEN_RESULTCONFIRM;
+ stp = LIST_FIRST(&ownerstp->ls_open);
+ stp->ls_flags = (new_stp->ls_flags & NFSLCK_SHAREBITS) |
+ NFSLCK_OPEN;
+ stp->ls_stateid.seqid = 0;
+ stp->ls_uid = new_stp->ls_uid;
+ if (lfp != stp->ls_lfp) {
+ LIST_REMOVE(stp, ls_file);
+ LIST_INSERT_HEAD(&lfp->lf_open, stp, ls_file);
+ stp->ls_lfp = lfp;
+ }
+ openstp = stp;
+ } else if (openstp) {
+ openstp->ls_flags |= (new_stp->ls_flags & NFSLCK_SHAREBITS);
+ openstp->ls_stateid.seqid++;
+
+ /*
+ * This is where we can choose to issue a delegation.
+ */
+ if (delegate && nfsrv_issuedelegs &&
+ writedeleg && !NFSVNO_EXRDONLY(exp) &&
+ (nfsrv_writedelegifpos || !readonly) &&
+ (clp->lc_flags & (LCL_CALLBACKSON | LCL_CBDOWN)) ==
+ LCL_CALLBACKSON &&
+ !NFSRV_V4DELEGLIMIT(nfsrv_delegatecnt) &&
+ NFSVNO_DELEGOK(vp)) {
+ new_deleg->ls_stateid.seqid = delegstateidp->seqid = 0;
+ new_deleg->ls_stateid.other[0] = delegstateidp->other[0]
+ = clp->lc_clientid.lval[0];
+ new_deleg->ls_stateid.other[1] = delegstateidp->other[1]
+ = clp->lc_clientid.lval[1];
+ new_deleg->ls_stateid.other[2] = delegstateidp->other[2]
+ = nfsrv_nextstateindex(clp);
+ new_deleg->ls_flags = (NFSLCK_DELEGWRITE |
+ NFSLCK_READACCESS | NFSLCK_WRITEACCESS);
+ *rflagsp |= NFSV4OPEN_WRITEDELEGATE;
+ new_deleg->ls_uid = new_stp->ls_uid;
+ new_deleg->ls_lfp = lfp;
+ new_deleg->ls_clp = clp;
+ new_deleg->ls_filerev = filerev;
+ new_deleg->ls_compref = nd->nd_compref;
+ LIST_INSERT_HEAD(&lfp->lf_deleg, new_deleg, ls_file);
+ LIST_INSERT_HEAD(NFSSTATEHASH(clp,
+ new_deleg->ls_stateid), new_deleg, ls_hash);
+ LIST_INSERT_HEAD(&clp->lc_deleg, new_deleg, ls_list);
+ new_deleg = NULL;
+ newnfsstats.srvdelegates++;
+ nfsrv_openpluslock++;
+ nfsrv_delegatecnt++;
+ }
+ } else {
+ new_open->ls_stateid.seqid = 0;
+ new_open->ls_stateid.other[0] = clp->lc_clientid.lval[0];
+ new_open->ls_stateid.other[1] = clp->lc_clientid.lval[1];
+ new_open->ls_stateid.other[2] = nfsrv_nextstateindex(clp);
+ new_open->ls_flags = (new_stp->ls_flags & NFSLCK_SHAREBITS)|
+ NFSLCK_OPEN;
+ new_open->ls_uid = new_stp->ls_uid;
+ new_open->ls_openowner = ownerstp;
+ new_open->ls_lfp = lfp;
+ new_open->ls_clp = clp;
+ LIST_INIT(&new_open->ls_open);
+ LIST_INSERT_HEAD(&lfp->lf_open, new_open, ls_file);
+ LIST_INSERT_HEAD(&ownerstp->ls_open, new_open, ls_list);
+ LIST_INSERT_HEAD(NFSSTATEHASH(clp, new_open->ls_stateid),
+ new_open, ls_hash);
+ openstp = new_open;
+ new_open = NULL;
+ newnfsstats.srvopens++;
+ nfsrv_openpluslock++;
+
+ /*
+ * This is where we can choose to issue a delegation.
+ */
+ if (delegate && nfsrv_issuedelegs &&
+ (writedeleg || readonly) &&
+ (clp->lc_flags & (LCL_CALLBACKSON | LCL_CBDOWN)) ==
+ LCL_CALLBACKSON &&
+ !NFSRV_V4DELEGLIMIT(nfsrv_delegatecnt) &&
+ NFSVNO_DELEGOK(vp)) {
+ new_deleg->ls_stateid.seqid = delegstateidp->seqid = 0;
+ new_deleg->ls_stateid.other[0] = delegstateidp->other[0]
+ = clp->lc_clientid.lval[0];
+ new_deleg->ls_stateid.other[1] = delegstateidp->other[1]
+ = clp->lc_clientid.lval[1];
+ new_deleg->ls_stateid.other[2] = delegstateidp->other[2]
+ = nfsrv_nextstateindex(clp);
+ if (writedeleg && !NFSVNO_EXRDONLY(exp) &&
+ (nfsrv_writedelegifpos || !readonly)) {
+ new_deleg->ls_flags = (NFSLCK_DELEGWRITE |
+ NFSLCK_READACCESS | NFSLCK_WRITEACCESS);
+ *rflagsp |= NFSV4OPEN_WRITEDELEGATE;
+ } else {
+ new_deleg->ls_flags = (NFSLCK_DELEGREAD |
+ NFSLCK_READACCESS);
+ *rflagsp |= NFSV4OPEN_READDELEGATE;
+ }
+ new_deleg->ls_uid = new_stp->ls_uid;
+ new_deleg->ls_lfp = lfp;
+ new_deleg->ls_clp = clp;
+ new_deleg->ls_filerev = filerev;
+ new_deleg->ls_compref = nd->nd_compref;
+ LIST_INSERT_HEAD(&lfp->lf_deleg, new_deleg, ls_file);
+ LIST_INSERT_HEAD(NFSSTATEHASH(clp,
+ new_deleg->ls_stateid), new_deleg, ls_hash);
+ LIST_INSERT_HEAD(&clp->lc_deleg, new_deleg, ls_list);
+ new_deleg = NULL;
+ newnfsstats.srvdelegates++;
+ nfsrv_openpluslock++;
+ nfsrv_delegatecnt++;
+ }
+ }
+ } else {
+ /*
+ * New owner case. Start the open_owner sequence with a
+ * Needs confirmation (unless a reclaim) and hang the
+ * new open off it.
+ */
+ new_open->ls_stateid.seqid = 0;
+ new_open->ls_stateid.other[0] = clp->lc_clientid.lval[0];
+ new_open->ls_stateid.other[1] = clp->lc_clientid.lval[1];
+ new_open->ls_stateid.other[2] = nfsrv_nextstateindex(clp);
+ new_open->ls_flags = (new_stp->ls_flags & NFSLCK_SHAREBITS) |
+ NFSLCK_OPEN;
+ new_open->ls_uid = new_stp->ls_uid;
+ LIST_INIT(&new_open->ls_open);
+ new_open->ls_openowner = new_stp;
+ new_open->ls_lfp = lfp;
+ new_open->ls_clp = clp;
+ LIST_INSERT_HEAD(&lfp->lf_open, new_open, ls_file);
+ if (new_stp->ls_flags & NFSLCK_RECLAIM) {
+ new_stp->ls_flags = 0;
+ } else {
+ *rflagsp |= NFSV4OPEN_RESULTCONFIRM;
+ new_stp->ls_flags = NFSLCK_NEEDSCONFIRM;
+ }
+ nfsrvd_refcache(new_stp->ls_op);
+ new_stp->ls_noopens = 0;
+ LIST_INIT(&new_stp->ls_open);
+ LIST_INSERT_HEAD(&new_stp->ls_open, new_open, ls_list);
+ LIST_INSERT_HEAD(&clp->lc_open, new_stp, ls_list);
+ LIST_INSERT_HEAD(NFSSTATEHASH(clp, new_open->ls_stateid),
+ new_open, ls_hash);
+ openstp = new_open;
+ new_open = NULL;
+ *new_stpp = NULL;
+ newnfsstats.srvopens++;
+ nfsrv_openpluslock++;
+ newnfsstats.srvopenowners++;
+ nfsrv_openpluslock++;
+ }
+ if (!error) {
+ stateidp->seqid = openstp->ls_stateid.seqid;
+ stateidp->other[0] = openstp->ls_stateid.other[0];
+ stateidp->other[1] = openstp->ls_stateid.other[1];
+ stateidp->other[2] = openstp->ls_stateid.other[2];
+ }
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ if (new_open)
+ FREE((caddr_t)new_open, M_NFSDSTATE);
+ if (new_deleg)
+ FREE((caddr_t)new_deleg, M_NFSDSTATE);
+ return (error);
+}
+
+/*
+ * Open update. Does the confirm, downgrade and close.
+ */
+APPLESTATIC int
+nfsrv_openupdate(vnode_t vp, struct nfsstate *new_stp, nfsquad_t clientid,
+ nfsv4stateid_t *stateidp, struct nfsrv_descript *nd, NFSPROC_T *p)
+{
+ struct nfsstate *stp, *ownerstp;
+ struct nfsclient *clp;
+ struct nfslockfile *lfp;
+ u_int32_t bits;
+ int error, gotstate = 0, len = 0, ret, freedlock;
+ u_char client[NFSV4_OPAQUELIMIT];
+
+ /*
+ * Check for restart conditions (client and server).
+ */
+ error = nfsrv_checkrestart(clientid, new_stp->ls_flags,
+ &new_stp->ls_stateid, 0);
+ if (error)
+ return (error);
+
+ NFSLOCKSTATE();
+ /*
+ * Get the open structure via clientid and stateid.
+ */
+ error = nfsrv_getclient(clientid, CLOPS_RENEW, &clp,
+ (nfsquad_t)((u_quad_t)0), NULL, p);
+ if (!error)
+ error = nfsrv_getstate(clp, &new_stp->ls_stateid,
+ new_stp->ls_flags, &stp);
+
+ /*
+ * Sanity check the open.
+ */
+ if (!error && (!(stp->ls_flags & NFSLCK_OPEN) ||
+ (!(new_stp->ls_flags & NFSLCK_CONFIRM) &&
+ (stp->ls_openowner->ls_flags & NFSLCK_NEEDSCONFIRM)) ||
+ ((new_stp->ls_flags & NFSLCK_CONFIRM) &&
+ (!(stp->ls_openowner->ls_flags & NFSLCK_NEEDSCONFIRM)))))
+ error = NFSERR_BADSTATEID;
+
+ if (!error)
+ error = nfsrv_checkseqid(nd, new_stp->ls_seq,
+ stp->ls_openowner, new_stp->ls_op);
+ if (!error && stp->ls_stateid.seqid != new_stp->ls_stateid.seqid &&
+ !(new_stp->ls_flags & NFSLCK_CONFIRM))
+ error = NFSERR_OLDSTATEID;
+ if (!error && vnode_vtype(vp) != VREG) {
+ if (vnode_vtype(vp) == VDIR)
+ error = NFSERR_ISDIR;
+ else
+ error = NFSERR_INVAL;
+ }
+
+ if (error) {
+ /*
+ * If a client tries to confirm an Open with a bad
+ * seqid# and there are no byte range locks or other Opens
+ * on the openowner, just throw it away, so the next use of the
+ * openowner will start a fresh seq#.
+ */
+ if (error == NFSERR_BADSEQID &&
+ (new_stp->ls_flags & NFSLCK_CONFIRM) &&
+ nfsrv_nootherstate(stp))
+ nfsrv_freeopenowner(stp->ls_openowner, 0, p);
+ NFSUNLOCKSTATE();
+ return (error);
+ }
+
+ /*
+ * Set the return stateid.
+ */
+ stateidp->seqid = stp->ls_stateid.seqid + 1;
+ stateidp->other[0] = stp->ls_stateid.other[0];
+ stateidp->other[1] = stp->ls_stateid.other[1];
+ stateidp->other[2] = stp->ls_stateid.other[2];
+ /*
+ * Now, handle the three cases.
+ */
+ if (new_stp->ls_flags & NFSLCK_CONFIRM) {
+ /*
+ * If the open doesn't need confirmation, it seems to me that
+ * there is a client error, but I'll just log it and keep going?
+ */
+ if (!(stp->ls_openowner->ls_flags & NFSLCK_NEEDSCONFIRM))
+ printf("Nfsv4d: stray open confirm\n");
+ stp->ls_openowner->ls_flags = 0;
+ stp->ls_stateid.seqid++;
+ if (!(clp->lc_flags & LCL_STAMPEDSTABLE)) {
+ clp->lc_flags |= LCL_STAMPEDSTABLE;
+ len = clp->lc_idlen;
+ NFSBCOPY(clp->lc_id, client, len);
+ gotstate = 1;
+ }
+ NFSUNLOCKSTATE();
+ } else if (new_stp->ls_flags & NFSLCK_CLOSE) {
+ ownerstp = stp->ls_openowner;
+ lfp = stp->ls_lfp;
+ freedlock = 0;
+ ret = nfsrv_freeopen(stp, &freedlock, 0, p);
+ /* See comment on nfsrv_lockctrl() w.r.t. locallocks. */
+ if (ret) {
+ lfp = NULL;
+ } else {
+ if (LIST_EMPTY(&lfp->lf_lock))
+ lfp = NULL;
+ }
+ /*
+ * For now, I won't do this. The openowner should be
+ * free'd in NFSNOOPEN seconds and it will be deref'd then.
+ if (LIST_EMPTY(&ownerstp->ls_open) && ownerstp->ls_op) {
+ nfsrvd_derefcache(ownerstp->ls_op);
+ ownerstp->ls_op = NULL;
+ }
+ */
+ NFSUNLOCKSTATE();
+ if (freedlock && lfp != NULL)
+ nfsrv_locallocks(vp, lfp, p);
+ } else {
+ /*
+ * Update the share bits, making sure that the new set are a
+ * subset of the old ones.
+ */
+ bits = (new_stp->ls_flags & NFSLCK_SHAREBITS);
+ if (~(stp->ls_flags) & bits) {
+ NFSUNLOCKSTATE();
+ return (NFSERR_INVAL);
+ }
+ stp->ls_flags = (bits | NFSLCK_OPEN);
+ stp->ls_stateid.seqid++;
+ NFSUNLOCKSTATE();
+ }
+
+ /*
+ * If the client just confirmed its first open, write a timestamp
+ * to the stable storage file.
+ */
+ if (gotstate)
+ nfsrv_writestable(client, len, NFSNST_NEWSTATE, p);
+ return (error);
+}
+
+/*
+ * Delegation update. Does the purge and return.
+ */
+APPLESTATIC int
+nfsrv_delegupdate(nfsquad_t clientid, nfsv4stateid_t *stateidp,
+ vnode_t vp, int op, struct ucred *cred, NFSPROC_T *p)
+{
+ struct nfsstate *stp;
+ struct nfsclient *clp;
+ int error;
+ fhandle_t fh;
+
+ /*
+ * Do a sanity check against the file handle for DelegReturn.
+ */
+ if (vp) {
+ error = nfsvno_getfh(vp, &fh, p);
+ if (error)
+ return (error);
+ }
+ /*
+ * Check for restart conditions (client and server).
+ */
+ if (op == NFSV4OP_DELEGRETURN)
+ error = nfsrv_checkrestart(clientid, NFSLCK_DELEGRETURN,
+ stateidp, 0);
+ else
+ error = nfsrv_checkrestart(clientid, NFSLCK_DELEGPURGE,
+ stateidp, 0);
+
+ NFSLOCKSTATE();
+ /*
+ * Get the open structure via clientid and stateid.
+ */
+ if (!error)
+ error = nfsrv_getclient(clientid, CLOPS_RENEW, &clp,
+ (nfsquad_t)((u_quad_t)0), NULL, p);
+ if (error) {
+ if (error == NFSERR_CBPATHDOWN)
+ error = 0;
+ if (error == NFSERR_STALECLIENTID && op == NFSV4OP_DELEGRETURN)
+ error = NFSERR_STALESTATEID;
+ }
+ if (!error && op == NFSV4OP_DELEGRETURN) {
+ error = nfsrv_getstate(clp, stateidp, NFSLCK_DELEGRETURN, &stp);
+ if (!error && stp->ls_stateid.seqid != stateidp->seqid)
+ error = NFSERR_OLDSTATEID;
+ }
+ /*
+ * NFSERR_EXPIRED means that the state has gone away,
+ * so Delegations have been purged. Just return ok.
+ */
+ if (error == NFSERR_EXPIRED && op == NFSV4OP_DELEGPURGE) {
+ NFSUNLOCKSTATE();
+ return (0);
+ }
+ if (error) {
+ NFSUNLOCKSTATE();
+ return (error);
+ }
+
+ if (op == NFSV4OP_DELEGRETURN) {
+ if (NFSBCMP((caddr_t)&fh, (caddr_t)&stp->ls_lfp->lf_fh,
+ sizeof (fhandle_t))) {
+ NFSUNLOCKSTATE();
+ return (NFSERR_BADSTATEID);
+ }
+ nfsrv_freedeleg(stp);
+ } else {
+ nfsrv_freedeleglist(&clp->lc_olddeleg);
+ }
+ NFSUNLOCKSTATE();
+ return (0);
+}
+
+/*
+ * Release lock owner.
+ */
+APPLESTATIC int
+nfsrv_releaselckown(struct nfsstate *new_stp, nfsquad_t clientid,
+ NFSPROC_T *p)
+{
+ struct nfsstate *stp, *nstp, *openstp, *ownstp;
+ struct nfsclient *clp;
+ int error;
+
+ /*
+ * Check for restart conditions (client and server).
+ */
+ error = nfsrv_checkrestart(clientid, new_stp->ls_flags,
+ &new_stp->ls_stateid, 0);
+ if (error)
+ return (error);
+
+ NFSLOCKSTATE();
+ /*
+ * Get the lock owner by name.
+ */
+ error = nfsrv_getclient(clientid, CLOPS_RENEW, &clp,
+ (nfsquad_t)((u_quad_t)0), NULL, p);
+ if (error) {
+ NFSUNLOCKSTATE();
+ return (error);
+ }
+ LIST_FOREACH(ownstp, &clp->lc_open, ls_list) {
+ LIST_FOREACH(openstp, &ownstp->ls_open, ls_list) {
+ stp = LIST_FIRST(&openstp->ls_open);
+ while (stp != LIST_END(&openstp->ls_open)) {
+ nstp = LIST_NEXT(stp, ls_list);
+ /*
+ * If the owner matches, check for locks and
+ * then free or return an error.
+ */
+ if (stp->ls_ownerlen == new_stp->ls_ownerlen &&
+ !NFSBCMP(stp->ls_owner, new_stp->ls_owner,
+ stp->ls_ownerlen)){
+ if (LIST_EMPTY(&stp->ls_lock)) {
+ (void) nfsrv_freelockowner(stp, NULL, 0, p);
+ } else {
+ NFSUNLOCKSTATE();
+ return (NFSERR_LOCKSHELD);
+ }
+ }
+ stp = nstp;
+ }
+ }
+ }
+ NFSUNLOCKSTATE();
+ return (0);
+}
+
+/*
+ * Get the file handle for a lock structure.
+ */
+static int
+nfsrv_getlockfh(vnode_t vp, u_short flags,
+ struct nfslockfile **new_lfpp, fhandle_t *nfhp, NFSPROC_T *p)
+{
+ fhandle_t *fhp = NULL;
+ struct nfslockfile *new_lfp;
+ int error;
+
+ /*
+ * For lock, use the new nfslock structure, otherwise just
+ * a fhandle_t on the stack.
+ */
+ if (flags & NFSLCK_OPEN) {
+ new_lfp = *new_lfpp;
+ fhp = &new_lfp->lf_fh;
+ } else if (nfhp) {
+ fhp = nfhp;
+ } else {
+ panic("nfsrv_getlockfh");
+ }
+ error = nfsvno_getfh(vp, fhp, p);
+ return (error);
+}
+
+/*
+ * Get an nfs lock structure. Allocate one, as required, and return a
+ * pointer to it.
+ * Returns an NFSERR_xxx upon failure or -1 to indicate no current lock.
+ */
+static int
+nfsrv_getlockfile(u_short flags, struct nfslockfile **new_lfpp,
+ struct nfslockfile **lfpp, fhandle_t *nfhp)
+{
+ struct nfslockfile *lfp;
+ fhandle_t *fhp = NULL, *tfhp;
+ struct nfslockhashhead *hp;
+ struct nfslockfile *new_lfp = NULL;
+
+ /*
+ * For lock, use the new nfslock structure, otherwise just
+ * a fhandle_t on the stack.
+ */
+ if (flags & NFSLCK_OPEN) {
+ new_lfp = *new_lfpp;
+ fhp = &new_lfp->lf_fh;
+ } else if (nfhp) {
+ fhp = nfhp;
+ } else {
+ panic("nfsrv_getlockfile");
+ }
+
+ hp = NFSLOCKHASH(fhp);
+ LIST_FOREACH(lfp, hp, lf_hash) {
+ tfhp = &lfp->lf_fh;
+ if (NFSVNO_CMPFH(fhp, tfhp)) {
+ *lfpp = lfp;
+ return (0);
+ }
+ }
+ if (!(flags & NFSLCK_OPEN))
+ return (-1);
+
+ /*
+ * No match, so chain the new one into the list.
+ */
+ LIST_INIT(&new_lfp->lf_open);
+ LIST_INIT(&new_lfp->lf_lock);
+ LIST_INIT(&new_lfp->lf_deleg);
+ LIST_INSERT_HEAD(hp, new_lfp, lf_hash);
+ *lfpp = new_lfp;
+ *new_lfpp = NULL;
+ return (0);
+}
+
+/*
+ * This function adds a nfslock lock structure to the list for the associated
+ * nfsstate and nfslockfile structures. It will be inserted after the
+ * entry pointed at by insert_lop.
+ * Must be called with soft clock interrupts disabled.
+ */
+static void
+nfsrv_insertlock(struct nfslock *new_lop, struct nfslock *insert_lop,
+ struct nfsstate *stp, struct nfslockfile *lfp)
+{
+ struct nfslock *lop, *nlop;
+
+ new_lop->lo_stp = stp;
+ new_lop->lo_lfp = lfp;
+
+ /* Insert in increasing lo_first order */
+ lop = LIST_FIRST(&lfp->lf_lock);
+ if (lop == LIST_END(&lfp->lf_lock) ||
+ new_lop->lo_first <= lop->lo_first) {
+ LIST_INSERT_HEAD(&lfp->lf_lock, new_lop, lo_lckfile);
+ } else {
+ nlop = LIST_NEXT(lop, lo_lckfile);
+ while (nlop != LIST_END(&lfp->lf_lock) &&
+ nlop->lo_first < new_lop->lo_first) {
+ lop = nlop;
+ nlop = LIST_NEXT(lop, lo_lckfile);
+ }
+ LIST_INSERT_AFTER(lop, new_lop, lo_lckfile);
+ }
+
+ /*
+ * Insert after insert_lop, which is overloaded as stp for
+ * an empty list.
+ */
+ if ((struct nfsstate *)insert_lop == stp)
+ LIST_INSERT_HEAD(&stp->ls_lock, new_lop, lo_lckowner);
+ else
+ LIST_INSERT_AFTER(insert_lop, new_lop, lo_lckowner);
+ newnfsstats.srvlocks++;
+ nfsrv_openpluslock++;
+}
+
+/*
+ * This function updates the locking for a lock owner and given file. It
+ * maintains a list of lock ranges ordered on increasing file offset that
+ * are NFSLCK_READ or NFSLCK_WRITE and non-overlapping (aka POSIX style).
+ * It always adds new_lop to the list and sometimes uses the one pointed
+ * at by other_lopp.
+ * Must be called with soft clock interrupts disabled.
+ */
+static void
+nfsrv_updatelock(struct nfsstate *stp, struct nfslock **new_lopp,
+ struct nfslock **other_lopp, struct nfslockfile *lfp)
+{
+ struct nfslock *new_lop = *new_lopp;
+ struct nfslock *lop, *tlop, *ilop;
+ struct nfslock *other_lop = *other_lopp;
+ int unlock = 0, myfile = 0;
+ u_int64_t tmp;
+
+ /*
+ * Work down the list until the lock is merged.
+ */
+ if (new_lop->lo_flags & NFSLCK_UNLOCK)
+ unlock = 1;
+ ilop = (struct nfslock *)stp;
+ lop = LIST_FIRST(&stp->ls_lock);
+ while (lop != LIST_END(&stp->ls_lock)) {
+ /*
+ * Only check locks for this file that aren't before the start of
+ * new lock's range.
+ */
+ if (lop->lo_lfp == lfp) {
+ myfile = 1;
+ if (lop->lo_end >= new_lop->lo_first) {
+ if (new_lop->lo_end < lop->lo_first) {
+ /*
+ * If the new lock ends before the start of the
+ * current lock's range, no merge, just insert
+ * the new lock.
+ */
+ break;
+ }
+ if (new_lop->lo_flags == lop->lo_flags ||
+ (new_lop->lo_first <= lop->lo_first &&
+ new_lop->lo_end >= lop->lo_end)) {
+ /*
+ * This lock can be absorbed by the new lock/unlock.
+ * This happens when it covers the entire range
+ * of the old lock or is contiguous
+ * with the old lock and is of the same type or an
+ * unlock.
+ */
+ if (lop->lo_first < new_lop->lo_first)
+ new_lop->lo_first = lop->lo_first;
+ if (lop->lo_end > new_lop->lo_end)
+ new_lop->lo_end = lop->lo_end;
+ tlop = lop;
+ lop = LIST_NEXT(lop, lo_lckowner);
+ nfsrv_freenfslock(tlop);
+ continue;
+ }
+
+ /*
+ * All these cases are for contiguous locks that are not the
+ * same type, so they can't be merged.
+ */
+ if (new_lop->lo_first <= lop->lo_first) {
+ /*
+ * This case is where the new lock overlaps with the
+ * first part of the old lock. Move the start of the
+ * old lock to just past the end of the new lock. The
+ * new lock will be inserted in front of the old, since
+ * ilop hasn't been updated. (We are done now.)
+ */
+ lop->lo_first = new_lop->lo_end;
+ break;
+ }
+ if (new_lop->lo_end >= lop->lo_end) {
+ /*
+ * This case is where the new lock overlaps with the
+ * end of the old lock's range. Move the old lock's
+ * end to just before the new lock's first and insert
+ * the new lock after the old lock.
+ * Might not be done yet, since the new lock could
+ * overlap further locks with higher ranges.
+ */
+ lop->lo_end = new_lop->lo_first;
+ ilop = lop;
+ lop = LIST_NEXT(lop, lo_lckowner);
+ continue;
+ }
+ /*
+ * The final case is where the new lock's range is in the
+ * middle of the current lock's and splits the current lock
+ * up. Use *other_lopp to handle the second part of the
+ * split old lock range. (We are done now.)
+ * For unlock, we use new_lop as other_lop and tmp, since
+ * other_lop and new_lop are the same for this case.
+ * We noted the unlock case above, so we don't need
+ * new_lop->lo_flags any longer.
+ */
+ tmp = new_lop->lo_first;
+ if (other_lop == NULL) {
+ if (!unlock)
+ panic("nfsd srv update unlock");
+ other_lop = new_lop;
+ *new_lopp = NULL;
+ }
+ other_lop->lo_first = new_lop->lo_end;
+ other_lop->lo_end = lop->lo_end;
+ other_lop->lo_flags = lop->lo_flags;
+ other_lop->lo_stp = stp;
+ other_lop->lo_lfp = lfp;
+ lop->lo_end = tmp;
+ nfsrv_insertlock(other_lop, lop, stp, lfp);
+ *other_lopp = NULL;
+ ilop = lop;
+ break;
+ }
+ }
+ ilop = lop;
+ lop = LIST_NEXT(lop, lo_lckowner);
+ if (myfile && (lop == LIST_END(&stp->ls_lock) ||
+ lop->lo_lfp != lfp))
+ break;
+ }
+
+ /*
+ * Insert the new lock in the list at the appropriate place.
+ */
+ if (!unlock) {
+ nfsrv_insertlock(new_lop, ilop, stp, lfp);
+ *new_lopp = NULL;
+ }
+}
+
+/*
+ * This function handles sequencing of locks, etc.
+ * It returns an error that indicates what the caller should do.
+ */
+static int
+nfsrv_checkseqid(struct nfsrv_descript *nd, u_int32_t seqid,
+ struct nfsstate *stp, struct nfsrvcache *op)
+{
+
+ if (op != nd->nd_rp)
+ panic("nfsrvstate checkseqid");
+ if (!(op->rc_flag & RC_INPROG))
+ panic("nfsrvstate not inprog");
+ if (stp->ls_op && stp->ls_op->rc_refcnt <= 0) {
+ printf("refcnt=%d\n", stp->ls_op->rc_refcnt);
+ panic("nfsrvstate op refcnt");
+ }
+ if ((stp->ls_seq + 1) == seqid) {
+ if (stp->ls_op)
+ nfsrvd_derefcache(stp->ls_op);
+ stp->ls_op = op;
+ nfsrvd_refcache(op);
+ stp->ls_seq = seqid;
+ return (0);
+ } else if (stp->ls_seq == seqid && stp->ls_op &&
+ op->rc_xid == stp->ls_op->rc_xid &&
+ op->rc_refcnt == 0 &&
+ op->rc_reqlen == stp->ls_op->rc_reqlen &&
+ op->rc_cksum == stp->ls_op->rc_cksum) {
+ if (stp->ls_op->rc_flag & RC_INPROG)
+ return (NFSERR_DONTREPLY);
+ nd->nd_rp = stp->ls_op;
+ nd->nd_rp->rc_flag |= RC_INPROG;
+ nfsrvd_delcache(op);
+ return (NFSERR_REPLYFROMCACHE);
+ }
+ return (NFSERR_BADSEQID);
+}
+
+/*
+ * Get the client ip address for callbacks. If the strings can't be parsed,
+ * just set lc_program to 0 to indicate no callbacks are possible.
+ * (For cases where the address can't be parsed or is 0.0.0.0.0.0, set
+ * the address to the client's transport address. This won't be used
+ * for callbacks, but can be printed out by newnfsstats for info.)
+ * Return error if the xdr can't be parsed, 0 otherwise.
+ */
+APPLESTATIC int
+nfsrv_getclientipaddr(struct nfsrv_descript *nd, struct nfsclient *clp)
+{
+ u_int32_t *tl;
+ u_char *cp, *cp2;
+ int i, j;
+ struct sockaddr_in *rad, *sad;
+ u_char protocol[5], addr[24];
+ int error = 0, cantparse = 0;
+ union {
+ u_long ival;
+ u_char cval[4];
+ } ip;
+ union {
+ u_short sval;
+ u_char cval[2];
+ } port;
+
+ rad = NFSSOCKADDR(clp->lc_req.nr_nam, struct sockaddr_in *);
+ rad->sin_family = AF_INET;
+ rad->sin_len = sizeof (struct sockaddr_in);
+ rad->sin_addr.s_addr = 0;
+ rad->sin_port = 0;
+ clp->lc_req.nr_client = NULL;
+ clp->lc_req.nr_lock = 0;
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ i = fxdr_unsigned(int, *tl);
+ if (i >= 3 && i <= 4) {
+ error = nfsrv_mtostr(nd, protocol, i);
+ if (error)
+ goto nfsmout;
+ if (!strcmp(protocol, "tcp")) {
+ clp->lc_flags |= LCL_TCPCALLBACK;
+ clp->lc_req.nr_sotype = SOCK_STREAM;
+ clp->lc_req.nr_soproto = IPPROTO_TCP;
+ } else if (!strcmp(protocol, "udp")) {
+ clp->lc_req.nr_sotype = SOCK_DGRAM;
+ clp->lc_req.nr_soproto = IPPROTO_UDP;
+ } else {
+ cantparse = 1;
+ }
+ } else {
+ cantparse = 1;
+ if (i > 0) {
+ error = nfsm_advance(nd, NFSM_RNDUP(i), -1);
+ if (error)
+ goto nfsmout;
+ }
+ }
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ i = fxdr_unsigned(int, *tl);
+ if (i < 0) {
+ error = NFSERR_BADXDR;
+ goto nfsmout;
+ } else if (i == 0) {
+ cantparse = 1;
+ } else if (!cantparse && i <= 23 && i >= 11) {
+ error = nfsrv_mtostr(nd, addr, i);
+ if (error)
+ goto nfsmout;
+
+ /*
+ * Parse out the address fields. We expect 6 decimal numbers
+ * separated by '.'s.
+ */
+ cp = addr;
+ i = 0;
+ while (*cp && i < 6) {
+ cp2 = cp;
+ while (*cp2 && *cp2 != '.')
+ cp2++;
+ if (*cp2)
+ *cp2++ = '\0';
+ else if (i != 5) {
+ cantparse = 1;
+ break;
+ }
+ j = nfsrv_getipnumber(cp);
+ if (j >= 0) {
+ if (i < 4)
+ ip.cval[3 - i] = j;
+ else
+ port.cval[5 - i] = j;
+ } else {
+ cantparse = 1;
+ break;
+ }
+ cp = cp2;
+ i++;
+ }
+ if (!cantparse) {
+ if (ip.ival != 0x0) {
+ rad->sin_addr.s_addr = htonl(ip.ival);
+ rad->sin_port = htons(port.sval);
+ } else {
+ cantparse = 1;
+ }
+ }
+ } else {
+ cantparse = 1;
+ if (i > 0) {
+ error = nfsm_advance(nd, NFSM_RNDUP(i), -1);
+ if (error)
+ goto nfsmout;
+ }
+ }
+ if (cantparse) {
+ sad = NFSSOCKADDR(nd->nd_nam, struct sockaddr_in *);
+ rad->sin_addr.s_addr = sad->sin_addr.s_addr;
+ rad->sin_port = 0x0;
+ clp->lc_program = 0;
+ }
+nfsmout:
+ return (error);
+}
+
+/*
+ * Turn a string of up to three decimal digits into a number. Return -1 upon
+ * error.
+ */
+static int
+nfsrv_getipnumber(u_char *cp)
+{
+ int i = 0, j = 0;
+
+ while (*cp) {
+ if (j > 2 || *cp < '0' || *cp > '9')
+ return (-1);
+ i *= 10;
+ i += (*cp - '0');
+ cp++;
+ j++;
+ }
+ if (i < 256)
+ return (i);
+ return (-1);
+}
+
+/*
+ * This function checks for restart conditions.
+ */
+static int
+nfsrv_checkrestart(nfsquad_t clientid, u_int32_t flags,
+ nfsv4stateid_t *stateidp, int specialid)
+{
+ int ret;
+
+ /*
+ * First check for a server restart. Open, LockT, ReleaseLockOwner
+ * and DelegPurge have a clientid, the rest a stateid.
+ */
+ if (flags &
+ (NFSLCK_OPEN | NFSLCK_TEST | NFSLCK_RELEASE | NFSLCK_DELEGPURGE)) {
+ if (clientid.lval[0] != nfsrvboottime)
+ return (NFSERR_STALECLIENTID);
+ } else if (stateidp->other[0] != nfsrvboottime &&
+ specialid == 0)
+ return (NFSERR_STALESTATEID);
+
+ /*
+ * Read, Write, Setattr and LockT can return NFSERR_GRACE and do
+ * not use a lock/open owner seqid#, so the check can be done now.
+ * (The others will be checked, as required, later.)
+ */
+ if (!(flags & (NFSLCK_CHECK | NFSLCK_TEST)))
+ return (0);
+
+ NFSLOCKSTATE();
+ ret = nfsrv_checkgrace(flags);
+ NFSUNLOCKSTATE();
+ return (ret);
+}
+
+/*
+ * Check for grace.
+ */
+static int
+nfsrv_checkgrace(u_int32_t flags)
+{
+
+ if (nfsrv_stablefirst.nsf_flags & NFSNSF_GRACEOVER) {
+ if (flags & NFSLCK_RECLAIM)
+ return (NFSERR_NOGRACE);
+ } else {
+ if (!(flags & NFSLCK_RECLAIM))
+ return (NFSERR_GRACE);
+
+ /*
+ * If grace is almost over and we are still getting Reclaims,
+ * extend grace a bit.
+ */
+ if ((NFSD_MONOSEC + NFSRV_LEASEDELTA) >
+ nfsrv_stablefirst.nsf_eograce)
+ nfsrv_stablefirst.nsf_eograce = NFSD_MONOSEC +
+ NFSRV_LEASEDELTA;
+ }
+ return (0);
+}
+
+/*
+ * Do a server callback.
+ */
+static int
+nfsrv_docallback(struct nfsclient *clp, int procnum,
+ nfsv4stateid_t *stateidp, int trunc, fhandle_t *fhp,
+ struct nfsvattr *nap, nfsattrbit_t *attrbitp, NFSPROC_T *p)
+{
+ mbuf_t m;
+ u_int32_t *tl;
+ struct nfsrv_descript nfsd, *nd = &nfsd;
+ struct ucred *cred;
+ int error = 0;
+ u_int32_t callback;
+
+ cred = newnfs_getcred();
+ NFSLOCKSTATE(); /* mostly for lc_cbref++ */
+ if (clp->lc_flags & LCL_NEEDSCONFIRM) {
+ NFSUNLOCKSTATE();
+ panic("docallb");
+ }
+ clp->lc_cbref++;
+ /*
+ * First, fill in some of the fields of nd and cr.
+ */
+ nd->nd_flag = ND_NFSV4;
+ if (clp->lc_flags & LCL_GSS)
+ nd->nd_flag |= ND_KERBV;
+ nd->nd_repstat = 0;
+ cred->cr_uid = clp->lc_uid;
+ cred->cr_gid = clp->lc_gid;
+ cred->cr_groups[0] = clp->lc_gid;
+ callback = clp->lc_callback;
+ NFSUNLOCKSTATE();
+ cred->cr_ngroups = 1;
+
+ /*
+ * Get the first mbuf for the request.
+ */
+ MGET(m, M_WAIT, MT_DATA);
+ mbuf_setlen(m, 0);
+ nd->nd_mreq = nd->nd_mb = m;
+ nd->nd_bpos = NFSMTOD(m, caddr_t);
+
+ /*
+ * and build the callback request.
+ */
+ if (procnum == NFSV4OP_CBGETATTR) {
+ nd->nd_procnum = NFSV4PROC_CBCOMPOUND;
+ (void) nfsm_strtom(nd, "CB Getattr", 10);
+ NFSM_BUILD(tl, u_int32_t *, 4 * NFSX_UNSIGNED);
+ *tl++ = txdr_unsigned(NFSV4_MINORVERSION);
+ *tl++ = txdr_unsigned(callback);
+ *tl++ = txdr_unsigned(1);
+ *tl = txdr_unsigned(NFSV4OP_CBGETATTR);
+ (void) nfsm_fhtom(nd, (u_int8_t *)fhp, NFSX_MYFH, 0);
+ (void) nfsrv_putattrbit(nd, attrbitp);
+ } else if (procnum == NFSV4OP_CBRECALL) {
+ nd->nd_procnum = NFSV4PROC_CBCOMPOUND;
+ (void) nfsm_strtom(nd, "CB Recall", 9);
+ NFSM_BUILD(tl, u_int32_t *, 5 * NFSX_UNSIGNED + NFSX_STATEID);
+ *tl++ = txdr_unsigned(NFSV4_MINORVERSION);
+ *tl++ = txdr_unsigned(callback);
+ *tl++ = txdr_unsigned(1);
+ *tl++ = txdr_unsigned(NFSV4OP_CBRECALL);
+ *tl++ = txdr_unsigned(stateidp->seqid);
+ NFSBCOPY((caddr_t)stateidp->other, (caddr_t)tl,
+ NFSX_STATEIDOTHER);
+ tl += (NFSX_STATEIDOTHER / NFSX_UNSIGNED);
+ if (trunc)
+ *tl = newnfs_true;
+ else
+ *tl = newnfs_false;
+ (void) nfsm_fhtom(nd, (u_int8_t *)fhp, NFSX_MYFH, 0);
+ } else {
+ nd->nd_procnum = NFSV4PROC_CBNULL;
+ }
+
+ /*
+ * Call newnfs_connect(), as required, and then newnfs_request().
+ */
+ (void) newnfs_sndlock(&clp->lc_req.nr_lock);
+ if (clp->lc_req.nr_client == NULL) {
+ if (nd->nd_procnum == NFSV4PROC_CBNULL)
+ error = newnfs_connect(NULL, &clp->lc_req, cred,
+ NULL, 1);
+ else
+ error = newnfs_connect(NULL, &clp->lc_req, cred,
+ NULL, 3);
+ }
+ newnfs_sndunlock(&clp->lc_req.nr_lock);
+ if (!error) {
+ error = newnfs_request(nd, NULL, clp, &clp->lc_req, NULL,
+ NULL, cred, clp->lc_program, NFSV4_CBVERS, NULL, 1, NULL);
+ }
+ NFSFREECRED(cred);
+
+ /*
+ * If error is set here, the Callback path isn't working
+ * properly, so twiddle the appropriate LCL_ flags.
+ * (nd_repstat != 0 indicates the Callback path is working,
+ * but the callback failed on the client.)
+ */
+ if (error) {
+ /*
+ * Mark the callback pathway down, which disabled issuing
+ * of delegations and gets Renew to return NFSERR_CBPATHDOWN.
+ */
+ NFSLOCKSTATE();
+ clp->lc_flags |= LCL_CBDOWN;
+ NFSUNLOCKSTATE();
+ } else {
+ /*
+ * Callback worked. If the callback path was down, disable
+ * callbacks, so no more delegations will be issued. (This
+ * is done on the assumption that the callback pathway is
+ * flakey.)
+ */
+ NFSLOCKSTATE();
+ if (clp->lc_flags & LCL_CBDOWN)
+ clp->lc_flags &= ~(LCL_CBDOWN | LCL_CALLBACKSON);
+ NFSUNLOCKSTATE();
+ if (nd->nd_repstat)
+ error = nd->nd_repstat;
+ else if (procnum == NFSV4OP_CBGETATTR)
+ error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0,
+ NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL,
+ p, NULL);
+ mbuf_freem(nd->nd_mrep);
+ }
+ NFSLOCKSTATE();
+ clp->lc_cbref--;
+ if ((clp->lc_flags & LCL_WAKEUPWANTED) && clp->lc_cbref == 0) {
+ clp->lc_flags &= ~LCL_WAKEUPWANTED;
+ NFSUNLOCKSTATE();
+ wakeup((caddr_t)clp);
+ } else {
+ NFSUNLOCKSTATE();
+ }
+ return (error);
+}
+
+/*
+ * Return the next index# for a clientid. Mostly just increment and return
+ * the next one, but... if the 32bit unsigned does actually wrap around,
+ * reboot. This is here more for fun than practical purposes. At an
+ * average rate of one new client per second, it will wrap around in
+ * approximately 136 years. (I think the server will have been shut
+ * down or rebooted before then.)
+ */
+static u_int32_t
+nfsrv_nextclientindex(void)
+{
+ static u_int32_t client_index = 0;
+
+ client_index++;
+ if (client_index != 0)
+ return (client_index);
+
+ /*
+ * In practice, we'll never get here, but the reboot is here,
+ * just for fun. (client_index will not wrap around on any real server)
+ */
+ printf("you must reboot now\n");
+ return (0); /* Just to shut the compiler up */
+}
+
+/*
+ * Return the next index# for a stateid. Mostly just increment and return
+ * the next one, but... if the 32bit unsigned does actually wrap around
+ * (will a BSD server stay up that long?), find
+ * new start and end values.
+ */
+static u_int32_t
+nfsrv_nextstateindex(struct nfsclient *clp)
+{
+ struct nfsstate *stp;
+ int i;
+ u_int32_t canuse, min_index, max_index;
+
+ if (!(clp->lc_flags & LCL_INDEXNOTOK)) {
+ clp->lc_stateindex++;
+ if (clp->lc_stateindex != clp->lc_statemaxindex)
+ return (clp->lc_stateindex);
+ }
+
+ /*
+ * Yuck, we've hit the end.
+ * Look for a new min and max.
+ */
+ min_index = 0;
+ max_index = 0xffffffff;
+ for (i = 0; i < NFSSTATEHASHSIZE; i++) {
+ LIST_FOREACH(stp, &clp->lc_stateid[i], ls_hash) {
+ if (stp->ls_stateid.other[2] > 0x80000000) {
+ if (stp->ls_stateid.other[2] < max_index)
+ max_index = stp->ls_stateid.other[2];
+ } else {
+ if (stp->ls_stateid.other[2] > min_index)
+ min_index = stp->ls_stateid.other[2];
+ }
+ }
+ }
+
+ /*
+ * Yikes, highly unlikely, but I'll handle it anyhow.
+ */
+ if (min_index == 0x80000000 && max_index == 0x80000001) {
+ canuse = 0;
+ /*
+ * Loop around until we find an unused entry. Return that
+ * and set LCL_INDEXNOTOK, so the search will continue next time.
+ * (This is one of those rare cases where a goto is the
+ * cleanest way to code the loop.)
+ */
+tryagain:
+ for (i = 0; i < NFSSTATEHASHSIZE; i++) {
+ LIST_FOREACH(stp, &clp->lc_stateid[i], ls_hash) {
+ if (stp->ls_stateid.other[2] == canuse) {
+ canuse++;
+ goto tryagain;
+ }
+ }
+ }
+ clp->lc_flags |= LCL_INDEXNOTOK;
+ return (canuse);
+ }
+
+ /*
+ * Ok to start again from min + 1.
+ */
+ clp->lc_stateindex = min_index + 1;
+ clp->lc_statemaxindex = max_index;
+ clp->lc_flags &= ~LCL_INDEXNOTOK;
+ return (clp->lc_stateindex);
+}
+
+/*
+ * The following functions handle the stable storage file that deals with
+ * the edge conditions described in RFC3530 Sec. 8.6.3.
+ * The file is as follows:
+ * - a single record at the beginning that has the lease time of the
+ * previous server instance (before the last reboot) and the nfsrvboottime
+ * values for the previous server boots.
+ * These previous boot times are used to ensure that the current
+ * nfsrvboottime does not, somehow, get set to a previous one.
+ * (This is important so that Stale ClientIDs and StateIDs can
+ * be recognized.)
+ * The number of previous nfsvrboottime values preceeds the list.
+ * - followed by some number of appended records with:
+ * - client id string
+ * - flag that indicates it is a record revoking state via lease
+ * expiration or similar
+ * OR has successfully acquired state.
+ * These structures vary in length, with the client string at the end, up
+ * to NFSV4_OPAQUELIMIT in size.
+ *
+ * At the end of the grace period, the file is truncated, the first
+ * record is rewritten with updated information and any acquired state
+ * records for successful reclaims of state are written.
+ *
+ * Subsequent records are appended when the first state is issued to
+ * a client and when state is revoked for a client.
+ *
+ * When reading the file in, state issued records that come later in
+ * the file override older ones, since the append log is in cronological order.
+ * If, for some reason, the file can't be read, the grace period is
+ * immediately terminated and all reclaims get NFSERR_NOGRACE.
+ */
+
+/*
+ * Read in the stable storage file. Called by nfssvc() before the nfsd
+ * processes start servicing requests.
+ */
+APPLESTATIC void
+nfsrv_setupstable(NFSPROC_T *p)
+{
+ struct nfsrv_stablefirst *sf = &nfsrv_stablefirst;
+ struct nfsrv_stable *sp, *nsp;
+ struct nfst_rec *tsp;
+ int error, i, tryagain;
+ off_t off = 0;
+ size_t aresid, len;
+ struct timeval curtime;
+
+ /*
+ * If NFSNSF_UPDATEDONE is set, this is a restart of the nfsds without
+ * a reboot, so state has not been lost.
+ */
+ if (sf->nsf_flags & NFSNSF_UPDATEDONE)
+ return;
+ /*
+ * Set Grace over just until the file reads successfully.
+ */
+ NFSGETTIME(&curtime);
+ nfsrvboottime = curtime.tv_sec;
+ LIST_INIT(&sf->nsf_head);
+ sf->nsf_flags = (NFSNSF_GRACEOVER | NFSNSF_NEEDLOCK);
+ sf->nsf_eograce = NFSD_MONOSEC + NFSRV_LEASEDELTA;
+ if (sf->nsf_fp == NULL)
+ return;
+ error = NFSD_RDWR(UIO_READ, NFSFPVNODE(sf->nsf_fp),
+ (caddr_t)&sf->nsf_rec, sizeof (struct nfsf_rec), off, UIO_SYSSPACE,
+ 0, NFSFPCRED(sf->nsf_fp), &aresid, p);
+ if (error || aresid || sf->nsf_numboots == 0 ||
+ sf->nsf_numboots > NFSNSF_MAXNUMBOOTS)
+ return;
+
+ /*
+ * Now, read in the boottimes.
+ */
+ sf->nsf_bootvals = (time_t *)malloc((sf->nsf_numboots + 1) *
+ sizeof (time_t), M_TEMP, M_WAITOK);
+ off = sizeof (struct nfsf_rec);
+ error = NFSD_RDWR(UIO_READ, NFSFPVNODE(sf->nsf_fp),
+ (caddr_t)sf->nsf_bootvals, sf->nsf_numboots * sizeof (time_t), off,
+ UIO_SYSSPACE, 0, NFSFPCRED(sf->nsf_fp), &aresid, p);
+ if (error || aresid) {
+ free((caddr_t)sf->nsf_bootvals, M_TEMP);
+ sf->nsf_bootvals = NULL;
+ return;
+ }
+
+ /*
+ * Make sure this nfsrvboottime is different from all recorded
+ * previous ones.
+ */
+ do {
+ tryagain = 0;
+ for (i = 0; i < sf->nsf_numboots; i++) {
+ if (nfsrvboottime == sf->nsf_bootvals[i]) {
+ nfsrvboottime++;
+ tryagain = 1;
+ break;
+ }
+ }
+ } while (tryagain);
+
+ sf->nsf_flags |= NFSNSF_OK;
+ off += (sf->nsf_numboots * sizeof (time_t));
+
+ /*
+ * Read through the file, building a list of records for grace
+ * checking.
+ * Each record is between sizeof (struct nfst_rec) and
+ * sizeof (struct nfst_rec) + NFSV4_OPAQUELIMIT - 1
+ * and is actually sizeof (struct nfst_rec) + nst_len - 1.
+ */
+ tsp = (struct nfst_rec *)malloc(sizeof (struct nfst_rec) +
+ NFSV4_OPAQUELIMIT - 1, M_TEMP, M_WAITOK);
+ do {
+ error = NFSD_RDWR(UIO_READ, NFSFPVNODE(sf->nsf_fp),
+ (caddr_t)tsp, sizeof (struct nfst_rec) + NFSV4_OPAQUELIMIT - 1,
+ off, UIO_SYSSPACE, 0, NFSFPCRED(sf->nsf_fp), &aresid, p);
+ len = (sizeof (struct nfst_rec) + NFSV4_OPAQUELIMIT - 1) - aresid;
+ if (error || (len > 0 && (len < sizeof (struct nfst_rec) ||
+ len < (sizeof (struct nfst_rec) + tsp->len - 1)))) {
+ /*
+ * Yuck, the file has been corrupted, so just return
+ * after clearing out any restart state, so the grace period
+ * is over.
+ */
+ LIST_FOREACH_SAFE(sp, &sf->nsf_head, nst_list, nsp) {
+ LIST_REMOVE(sp, nst_list);
+ free((caddr_t)sp, M_TEMP);
+ }
+ free((caddr_t)tsp, M_TEMP);
+ sf->nsf_flags &= ~NFSNSF_OK;
+ free((caddr_t)sf->nsf_bootvals, M_TEMP);
+ sf->nsf_bootvals = NULL;
+ return;
+ }
+ if (len > 0) {
+ off += sizeof (struct nfst_rec) + tsp->len - 1;
+ /*
+ * Search the list for a matching client.
+ */
+ LIST_FOREACH(sp, &sf->nsf_head, nst_list) {
+ if (tsp->len == sp->nst_len &&
+ !NFSBCMP(tsp->client, sp->nst_client, tsp->len))
+ break;
+ }
+ if (sp == LIST_END(&sf->nsf_head)) {
+ sp = (struct nfsrv_stable *)malloc(tsp->len +
+ sizeof (struct nfsrv_stable) - 1, M_TEMP,
+ M_WAITOK);
+ NFSBCOPY((caddr_t)tsp, (caddr_t)&sp->nst_rec,
+ sizeof (struct nfst_rec) + tsp->len - 1);
+ LIST_INSERT_HEAD(&sf->nsf_head, sp, nst_list);
+ } else {
+ if (tsp->flag == NFSNST_REVOKE)
+ sp->nst_flag |= NFSNST_REVOKE;
+ else
+ /*
+ * A subsequent timestamp indicates the client
+ * did a setclientid/confirm and any previous
+ * revoke is no longer relevant.
+ */
+ sp->nst_flag &= ~NFSNST_REVOKE;
+ }
+ }
+ } while (len > 0);
+ free((caddr_t)tsp, M_TEMP);
+ sf->nsf_flags = NFSNSF_OK;
+ sf->nsf_eograce = NFSD_MONOSEC + sf->nsf_lease +
+ NFSRV_LEASEDELTA;
+}
+
+/*
+ * Update the stable storage file, now that the grace period is over.
+ */
+APPLESTATIC void
+nfsrv_updatestable(NFSPROC_T *p)
+{
+ struct nfsrv_stablefirst *sf = &nfsrv_stablefirst;
+ struct nfsrv_stable *sp, *nsp;
+ int i;
+ struct nfsvattr nva;
+ vnode_t vp;
+#if defined(__FreeBSD_version) && (__FreeBSD_version >= 500000)
+ mount_t mp = NULL;
+#endif
+ int error;
+
+ if (sf->nsf_fp == NULL || (sf->nsf_flags & NFSNSF_UPDATEDONE))
+ return;
+ sf->nsf_flags |= NFSNSF_UPDATEDONE;
+ /*
+ * Ok, we need to rewrite the stable storage file.
+ * - truncate to 0 length
+ * - write the new first structure
+ * - loop through the data structures, writing out any that
+ * have timestamps older than the old boot
+ */
+ if (sf->nsf_bootvals) {
+ sf->nsf_numboots++;
+ for (i = sf->nsf_numboots - 2; i >= 0; i--)
+ sf->nsf_bootvals[i + 1] = sf->nsf_bootvals[i];
+ } else {
+ sf->nsf_numboots = 1;
+ sf->nsf_bootvals = (time_t *)malloc(sizeof (time_t),
+ M_TEMP, M_WAITOK);
+ }
+ sf->nsf_bootvals[0] = nfsrvboottime;
+ sf->nsf_lease = nfsrv_lease;
+ NFSVNO_ATTRINIT(&nva);
+ NFSVNO_SETATTRVAL(&nva, size, 0);
+ vp = NFSFPVNODE(sf->nsf_fp);
+ NFS_STARTWRITE(vp, &mp);
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ error = nfsvno_setattr(vp, &nva, NFSFPCRED(sf->nsf_fp), p, NULL);
+ NFS_ENDWRITE(mp);
+ NFSVOPUNLOCK(vp, 0, p);
+ if (!error)
+ error = NFSD_RDWR(UIO_WRITE, vp,
+ (caddr_t)&sf->nsf_rec, sizeof (struct nfsf_rec), (off_t)0,
+ UIO_SYSSPACE, IO_SYNC, NFSFPCRED(sf->nsf_fp), NULL, p);
+ if (!error)
+ error = NFSD_RDWR(UIO_WRITE, vp,
+ (caddr_t)sf->nsf_bootvals,
+ sf->nsf_numboots * sizeof (time_t),
+ (off_t)(sizeof (struct nfsf_rec)),
+ UIO_SYSSPACE, IO_SYNC, NFSFPCRED(sf->nsf_fp), NULL, p);
+ free((caddr_t)sf->nsf_bootvals, M_TEMP);
+ sf->nsf_bootvals = NULL;
+ if (error) {
+ sf->nsf_flags &= ~NFSNSF_OK;
+ printf("EEK! Can't write NfsV4 stable storage file\n");
+ return;
+ }
+ sf->nsf_flags |= NFSNSF_OK;
+
+ /*
+ * Loop through the list and write out timestamp records for
+ * any clients that successfully reclaimed state.
+ */
+ LIST_FOREACH_SAFE(sp, &sf->nsf_head, nst_list, nsp) {
+ if (sp->nst_flag & NFSNST_GOTSTATE) {
+ nfsrv_writestable(sp->nst_client, sp->nst_len,
+ NFSNST_NEWSTATE, p);
+ sp->nst_clp->lc_flags |= LCL_STAMPEDSTABLE;
+ }
+ LIST_REMOVE(sp, nst_list);
+ free((caddr_t)sp, M_TEMP);
+ }
+}
+
+/*
+ * Append a record to the stable storage file.
+ */
+APPLESTATIC void
+nfsrv_writestable(u_char *client, int len, int flag, NFSPROC_T *p)
+{
+ struct nfsrv_stablefirst *sf = &nfsrv_stablefirst;
+ struct nfst_rec *sp;
+ int error;
+
+ if (!(sf->nsf_flags & NFSNSF_OK) || sf->nsf_fp == NULL)
+ return;
+ sp = (struct nfst_rec *)malloc(sizeof (struct nfst_rec) +
+ len - 1, M_TEMP, M_WAITOK);
+ sp->len = len;
+ NFSBCOPY(client, sp->client, len);
+ sp->flag = flag;
+ error = NFSD_RDWR(UIO_WRITE, NFSFPVNODE(sf->nsf_fp),
+ (caddr_t)sp, sizeof (struct nfst_rec) + len - 1, (off_t)0,
+ UIO_SYSSPACE, (IO_SYNC | IO_APPEND), NFSFPCRED(sf->nsf_fp), NULL, p);
+ free((caddr_t)sp, M_TEMP);
+ if (error) {
+ sf->nsf_flags &= ~NFSNSF_OK;
+ printf("EEK! Can't write NfsV4 stable storage file\n");
+ }
+}
+
+/*
+ * This function is called during the grace period to mark a client
+ * that successfully reclaimed state.
+ */
+static void
+nfsrv_markstable(struct nfsclient *clp)
+{
+ struct nfsrv_stable *sp;
+
+ /*
+ * First find the client structure.
+ */
+ LIST_FOREACH(sp, &nfsrv_stablefirst.nsf_head, nst_list) {
+ if (sp->nst_len == clp->lc_idlen &&
+ !NFSBCMP(sp->nst_client, clp->lc_id, sp->nst_len))
+ break;
+ }
+ if (sp == LIST_END(&nfsrv_stablefirst.nsf_head))
+ return;
+
+ /*
+ * Now, just mark it and set the nfsclient back pointer.
+ */
+ sp->nst_flag |= NFSNST_GOTSTATE;
+ sp->nst_clp = clp;
+}
+
+/*
+ * This function is called for a reclaim, to see if it gets grace.
+ * It returns 0 if a reclaim is allowed, 1 otherwise.
+ */
+static int
+nfsrv_checkstable(struct nfsclient *clp)
+{
+ struct nfsrv_stable *sp;
+
+ /*
+ * First, find the entry for the client.
+ */
+ LIST_FOREACH(sp, &nfsrv_stablefirst.nsf_head, nst_list) {
+ if (sp->nst_len == clp->lc_idlen &&
+ !NFSBCMP(sp->nst_client, clp->lc_id, sp->nst_len))
+ break;
+ }
+
+ /*
+ * If not in the list, state was revoked or no state was issued
+ * since the previous reboot, a reclaim is denied.
+ */
+ if (sp == LIST_END(&nfsrv_stablefirst.nsf_head) ||
+ (sp->nst_flag & NFSNST_REVOKE) ||
+ !(nfsrv_stablefirst.nsf_flags & NFSNSF_OK))
+ return (1);
+ return (0);
+}
+
+/*
+ * Test for and try to clear out a conflicting client. This is called by
+ * nfsrv_lockctrl() and nfsrv_openctrl() when conflicts with other clients
+ * a found.
+ * The trick here is that it can't revoke a conflicting client with an
+ * expired lease unless it holds the v4root lock, so...
+ * If no v4root lock, get the lock and return 1 to indicate "try again".
+ * Return 0 to indicate the conflict can't be revoked and 1 to indicate
+ * the revocation worked and the conflicting client is "bye, bye", so it
+ * can be tried again.
+ * Unlocks State before a non-zero value is returned.
+ */
+static int
+nfsrv_clientconflict(struct nfsclient *clp, int *haslockp, __unused vnode_t vp,
+ NFSPROC_T *p)
+{
+ int gotlock;
+
+ /*
+ * If lease hasn't expired, we can't fix it.
+ */
+ if (clp->lc_expiry >= NFSD_MONOSEC ||
+ !(nfsrv_stablefirst.nsf_flags & NFSNSF_UPDATEDONE))
+ return (0);
+ if (*haslockp == 0) {
+ NFSUNLOCKSTATE();
+ NFSVOPUNLOCK(vp, 0, p);
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_relref(&nfsv4rootfs_lock);
+ do {
+ gotlock = nfsv4_lock(&nfsv4rootfs_lock, 1, NULL,
+ NFSV4ROOTLOCKMUTEXPTR);
+ } while (!gotlock);
+ NFSUNLOCKV4ROOTMUTEX();
+ NFSLOCKSTATE(); /* to avoid a race with */
+ NFSUNLOCKSTATE(); /* nfsrv_servertimer() */
+ *haslockp = 1;
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ return (1);
+ }
+ NFSUNLOCKSTATE();
+
+ /*
+ * Ok, we can expire the conflicting client.
+ */
+ nfsrv_writestable(clp->lc_id, clp->lc_idlen, NFSNST_REVOKE, p);
+ nfsrv_cleanclient(clp, p);
+ nfsrv_freedeleglist(&clp->lc_deleg);
+ nfsrv_freedeleglist(&clp->lc_olddeleg);
+ LIST_REMOVE(clp, lc_hash);
+ nfsrv_zapclient(clp, p);
+ return (1);
+}
+
+
+/*
+ * Resolve a delegation conflict.
+ * Returns 0 to indicate the conflict was resolved without sleeping.
+ * Return -1 to indicate that the caller should check for conflicts again.
+ * Return > 0 for an error that should be returned, normally NFSERR_DELAY.
+ *
+ * Also, manipulate the nfsv4root_lock, as required. It isn't changed
+ * for a return of 0, since there was no sleep and it could be required
+ * later. It is released for a return of NFSERR_DELAY, since the caller
+ * will return that error. It is released when a sleep was done waiting
+ * for the delegation to be returned or expire (so that other nfsds can
+ * handle ops). Then, it must be acquired for the write to stable storage.
+ * (This function is somewhat similar to nfsrv_clientconflict(), but
+ * the semantics differ in a couple of subtle ways. The return of 0
+ * indicates the conflict was resolved without sleeping here, not
+ * that the conflict can't be resolved and the handling of nfsv4root_lock
+ * differs, as noted above.)
+ * Unlocks State before returning a non-zero value.
+ */
+static int
+nfsrv_delegconflict(struct nfsstate *stp, int *haslockp, NFSPROC_T *p,
+ __unused vnode_t vp)
+{
+ struct nfsclient *clp = stp->ls_clp;
+ int gotlock, error, retrycnt, zapped_clp;
+ nfsv4stateid_t tstateid;
+ fhandle_t tfh;
+
+ /*
+ * If the conflict is with an old delegation...
+ */
+ if (stp->ls_flags & NFSLCK_OLDDELEG) {
+ /*
+ * You can delete it, if it has expired.
+ */
+ if (clp->lc_delegtime < NFSD_MONOSEC) {
+ nfsrv_freedeleg(stp);
+ NFSUNLOCKSTATE();
+ return (-1);
+ }
+ NFSUNLOCKSTATE();
+ /*
+ * During this delay, the old delegation could expire or it
+ * could be recovered by the client via an Open with
+ * CLAIM_DELEGATE_PREV.
+ * Release the nfsv4root_lock, if held.
+ */
+ if (*haslockp) {
+ *haslockp = 0;
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (NFSERR_DELAY);
+ }
+
+ /*
+ * It's a current delegation, so:
+ * - check to see if the delegation has expired
+ * - if so, get the v4root lock and then expire it
+ */
+ if (!(stp->ls_flags & NFSLCK_DELEGRECALL)) {
+ /*
+ * - do a recall callback, since not yet done
+ * For now, never allow truncate to be set. To use
+ * truncate safely, it must be guaranteed that the
+ * Remove, Rename or Setattr with size of 0 will
+ * succeed and that would require major changes to
+ * the VFS/Vnode OPs.
+ * Set the expiry time large enough so that it won't expire
+ * until after the callback, then set it correctly, once
+ * the callback is done. (The delegation will now time
+ * out whether or not the Recall worked ok. The timeout
+ * will be extended when ops are done on the delegation
+ * stateid, up to the timelimit.)
+ */
+ stp->ls_delegtime = NFSD_MONOSEC + (2 * nfsrv_lease) +
+ NFSRV_LEASEDELTA;
+ stp->ls_delegtimelimit = NFSD_MONOSEC + (6 * nfsrv_lease) +
+ NFSRV_LEASEDELTA;
+ stp->ls_flags |= NFSLCK_DELEGRECALL;
+
+ /*
+ * Loop NFSRV_CBRETRYCNT times while the CBRecall replies
+ * NFSERR_BADSTATEID or NFSERR_BADHANDLE. This is done
+ * in order to try and avoid a race that could happen
+ * when a CBRecall request passed the Open reply with
+ * the delegation in it when transitting the network.
+ * Since nfsrv_docallback will sleep, don't use stp after
+ * the call.
+ */
+ NFSBCOPY((caddr_t)&stp->ls_stateid, (caddr_t)&tstateid,
+ sizeof (tstateid));
+ NFSBCOPY((caddr_t)&stp->ls_lfp->lf_fh, (caddr_t)&tfh,
+ sizeof (tfh));
+ NFSUNLOCKSTATE();
+ if (*haslockp) {
+ *haslockp = 0;
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ retrycnt = 0;
+ do {
+ error = nfsrv_docallback(clp, NFSV4OP_CBRECALL,
+ &tstateid, 0, &tfh, NULL, NULL, p);
+ retrycnt++;
+ } while ((error == NFSERR_BADSTATEID ||
+ error == NFSERR_BADHANDLE) && retrycnt < NFSV4_CBRETRYCNT);
+ return (NFSERR_DELAY);
+ }
+
+ if (clp->lc_expiry >= NFSD_MONOSEC &&
+ stp->ls_delegtime >= NFSD_MONOSEC) {
+ NFSUNLOCKSTATE();
+ /*
+ * A recall has been done, but it has not yet expired.
+ * So, RETURN_DELAY.
+ */
+ if (*haslockp) {
+ *haslockp = 0;
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (NFSERR_DELAY);
+ }
+
+ /*
+ * If we don't yet have the lock, just get it and then return,
+ * since we need that before deleting expired state, such as
+ * this delegation.
+ * When getting the lock, unlock the vnode, so other nfsds that
+ * are in progress, won't get stuck waiting for the vnode lock.
+ */
+ if (*haslockp == 0) {
+ NFSUNLOCKSTATE();
+ NFSVOPUNLOCK(vp, 0, p);
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_relref(&nfsv4rootfs_lock);
+ do {
+ gotlock = nfsv4_lock(&nfsv4rootfs_lock, 1, NULL,
+ NFSV4ROOTLOCKMUTEXPTR);
+ } while (!gotlock);
+ NFSUNLOCKV4ROOTMUTEX();
+ NFSLOCKSTATE(); /* to avoid a race with */
+ NFSUNLOCKSTATE(); /* nfsrv_servertimer() */
+ *haslockp = 1;
+ NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
+ return (-1);
+ }
+
+ NFSUNLOCKSTATE();
+ /*
+ * Ok, we can delete the expired delegation.
+ * First, write the Revoke record to stable storage and then
+ * clear out the conflict.
+ * Since all other nfsd threads are now blocked, we can safely
+ * sleep without the state changing.
+ */
+ nfsrv_writestable(clp->lc_id, clp->lc_idlen, NFSNST_REVOKE, p);
+ if (clp->lc_expiry < NFSD_MONOSEC) {
+ nfsrv_cleanclient(clp, p);
+ nfsrv_freedeleglist(&clp->lc_deleg);
+ nfsrv_freedeleglist(&clp->lc_olddeleg);
+ LIST_REMOVE(clp, lc_hash);
+ zapped_clp = 1;
+ } else {
+ nfsrv_freedeleg(stp);
+ zapped_clp = 0;
+ }
+ if (zapped_clp)
+ nfsrv_zapclient(clp, p);
+ return (-1);
+}
+
+/*
+ * Check for a remove allowed, if remove is set to 1 and get rid of
+ * delegations.
+ */
+APPLESTATIC int
+nfsrv_checkremove(vnode_t vp, int remove, NFSPROC_T *p)
+{
+ struct nfsstate *stp;
+ struct nfslockfile *lfp;
+ int error, haslock = 0;
+ fhandle_t nfh;
+
+ /*
+ * First, get the lock file structure.
+ * (A return of -1 means no associated state, so remove ok.)
+ */
+ error = nfsrv_getlockfh(vp, NFSLCK_CHECK, NULL, &nfh, p);
+tryagain:
+ NFSLOCKSTATE();
+ if (!error)
+ error = nfsrv_getlockfile(NFSLCK_CHECK, NULL, &lfp, &nfh);
+ if (error) {
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ if (error == -1)
+ return (0);
+ return (error);
+ }
+
+ /*
+ * Now, we must Recall any delegations.
+ */
+ error = nfsrv_cleandeleg(vp, lfp, NULL, &haslock, p);
+ if (error) {
+ /*
+ * nfsrv_cleandeleg() unlocks state for non-zero
+ * return.
+ */
+ if (error == -1)
+ goto tryagain;
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (error);
+ }
+
+ /*
+ * Now, look for a conflicting open share.
+ */
+ if (remove) {
+ LIST_FOREACH(stp, &lfp->lf_open, ls_file) {
+ if (stp->ls_flags & NFSLCK_WRITEDENY) {
+ error = NFSERR_FILEOPEN;
+ break;
+ }
+ }
+ }
+
+ NFSUNLOCKSTATE();
+ if (haslock) {
+ NFSLOCKV4ROOTMUTEX();
+ nfsv4_unlock(&nfsv4rootfs_lock, 1);
+ NFSUNLOCKV4ROOTMUTEX();
+ }
+ return (error);
+}
+
+/*
+ * Clear out all delegations for the file referred to by lfp.
+ * May return NFSERR_DELAY, if there will be a delay waiting for
+ * delegations to expire.
+ * Returns -1 to indicate it slept while recalling a delegation.
+ * This function has the side effect of deleting the nfslockfile structure,
+ * if it no longer has associated state and didn't have to sleep.
+ * Unlocks State before a non-zero value is returned.
+ */
+static int
+nfsrv_cleandeleg(vnode_t vp, struct nfslockfile *lfp,
+ struct nfsclient *clp, int *haslockp, NFSPROC_T *p)
+{
+ struct nfsstate *stp, *nstp;
+ int ret;
+
+ stp = LIST_FIRST(&lfp->lf_deleg);
+ while (stp != LIST_END(&lfp->lf_deleg)) {
+ nstp = LIST_NEXT(stp, ls_file);
+ if (stp->ls_clp != clp) {
+ ret = nfsrv_delegconflict(stp, haslockp, p, vp);
+ if (ret) {
+ /*
+ * nfsrv_delegconflict() unlocks state
+ * when it returns non-zero.
+ */
+ return (ret);
+ }
+ }
+ stp = nstp;
+ }
+ return (0);
+}
+
+/*
+ * There are certain operations that, when being done outside of NFSv4,
+ * require that any NFSv4 delegation for the file be recalled.
+ * This function is to be called for those cases:
+ * VOP_RENAME() - When a delegation is being recalled for any reason,
+ * the client may have to do Opens against the server, using the file's
+ * final component name. If the file has been renamed on the server,
+ * that component name will be incorrect and the Open will fail.
+ * VOP_REMOVE() - Theoretically, a client could Open a file after it has
+ * been removed on the server, if there is a delegation issued to
+ * that client for the file. I say "theoretically" since clients
+ * normally do an Access Op before the Open and that Access Op will
+ * fail with ESTALE. Note that NFSv2 and 3 don't even do Opens, so
+ * they will detect the file's removal in the same manner. (There is
+ * one case where RFC3530 allows a client to do an Open without first
+ * doing an Access Op, which is passage of a check against the ACE
+ * returned with a Write delegation, but current practice is to ignore
+ * the ACE and always do an Access Op.)
+ * Since the functions can only be called with an unlocked vnode, this
+ * can't be done at this time.
+ * VOP_ADVLOCK() - When a client holds a delegation, it can issue byte range
+ * locks locally in the client, which are not visible to the server. To
+ * deal with this, issuing of delegations for a vnode must be disabled
+ * and all delegations for the vnode recalled. This is done via the
+ * second function, using the VV_DISABLEDELEG vflag on the vnode.
+ */
+APPLESTATIC void
+nfsd_recalldelegation(vnode_t vp, NFSPROC_T *p)
+{
+ struct timespec mytime;
+ int32_t starttime;
+ int error;
+
+ KASSERT(!VOP_ISLOCKED(vp), ("vp %p is locked", vp));
+
+ /*
+ * First, check to see if the server is currently running and it has
+ * been called for a regular file when issuing delegations.
+ */
+ if (newnfs_numnfsd == 0 || vp->v_type != VREG ||
+ nfsrv_issuedelegs == 0)
+ return;
+
+ /*
+ * Now, call nfsrv_checkremove() in a loop while it returns
+ * NFSERR_DELAY. Return upon any other error or when timed out.
+ */
+ NFSGETNANOTIME(&mytime);
+ starttime = (u_int32_t)mytime.tv_sec;
+ do {
+ error = nfsrv_checkremove(vp, 0, p);
+ if (error == NFSERR_DELAY) {
+ NFSGETNANOTIME(&mytime);
+ if (((u_int32_t)mytime.tv_sec - starttime) >
+ NFS_REMOVETIMEO &&
+ ((u_int32_t)mytime.tv_sec - starttime) <
+ 100000)
+ return;
+ /* Sleep for a short period of time */
+ (void) nfs_catnap(PZERO, "nfsremove");
+ }
+ } while (error == NFSERR_DELAY);
+}
+
+APPLESTATIC void
+nfsd_disabledelegation(vnode_t vp, NFSPROC_T *p)
+{
+
+#ifdef VV_DISABLEDELEG
+ /*
+ * First, flag issuance of delegations disabled.
+ */
+ atomic_set_long(&vp->v_vflag, VV_DISABLEDELEG);
+#endif
+
+ /*
+ * Then call nfsd_recalldelegation() to get rid of all extant
+ * delegations.
+ */
+ nfsd_recalldelegation(vp, p);
+}
+
+/*
+ * Check for conflicting locks, etc. and then get rid of delegations.
+ * (At one point I thought that I should get rid of delegations for any
+ * Setattr, since it could potentially disallow the I/O op (read or write)
+ * allowed by the delegation. However, Setattr Ops that aren't changing
+ * the size get a stateid of all 0s, so you can't tell if it is a delegation
+ * for the same client or a different one, so I decided to only get rid
+ * of delegations for other clients when the size is being changed.)
+ * In general, a Setattr can disable NFS I/O Ops that are outstanding, such
+ * as Write backs, even if there is no delegation, so it really isn't any
+ * different?)
+ */
+APPLESTATIC int
+nfsrv_checksetattr(vnode_t vp, struct nfsrv_descript *nd,
+ nfsv4stateid_t *stateidp, struct nfsvattr *nvap, nfsattrbit_t *attrbitp,
+ struct nfsexstuff *exp, NFSPROC_T *p)
+{
+ struct nfsstate st, *stp = &st;
+ struct nfslock lo, *lop = &lo;
+ int error = 0;
+ nfsquad_t clientid;
+
+ if (NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_SIZE)) {
+ stp->ls_flags = (NFSLCK_CHECK | NFSLCK_WRITEACCESS);
+ lop->lo_first = nvap->na_size;
+ } else {
+ stp->ls_flags = 0;
+ lop->lo_first = 0;
+ }
+ if (NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_OWNER) ||
+ NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_OWNERGROUP) ||
+ NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_MODE) ||
+ NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_ACL))
+ stp->ls_flags |= NFSLCK_SETATTR;
+ if (stp->ls_flags == 0)
+ return (0);
+ lop->lo_end = NFS64BITSSET;
+ lop->lo_flags = NFSLCK_WRITE;
+ stp->ls_ownerlen = 0;
+ stp->ls_op = NULL;
+ stp->ls_uid = nd->nd_cred->cr_uid;
+ stp->ls_stateid.seqid = stateidp->seqid;
+ clientid.lval[0] = stp->ls_stateid.other[0] = stateidp->other[0];
+ clientid.lval[1] = stp->ls_stateid.other[1] = stateidp->other[1];
+ stp->ls_stateid.other[2] = stateidp->other[2];
+ error = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
+ stateidp, exp, nd, p);
+ return (error);
+}
+
+/*
+ * Check for a write delegation and do a CBGETATTR if there is one, updating
+ * the attributes, as required.
+ * Should I return an error if I can't get the attributes? (For now, I'll
+ * just return ok.
+ */
+APPLESTATIC int
+nfsrv_checkgetattr(struct nfsrv_descript *nd, vnode_t vp,
+ struct nfsvattr *nvap, nfsattrbit_t *attrbitp, struct ucred *cred,
+ NFSPROC_T *p)
+{
+ struct nfsstate *stp;
+ struct nfslockfile *lfp;
+ struct nfsclient *clp;
+ struct nfsvattr nva;
+ fhandle_t nfh;
+ int error;
+ nfsattrbit_t cbbits;
+ u_quad_t delegfilerev;
+
+ NFSCBGETATTR_ATTRBIT(attrbitp, &cbbits);
+ if (!NFSNONZERO_ATTRBIT(&cbbits))
+ return (0);
+
+ /*
+ * Get the lock file structure.
+ * (A return of -1 means no associated state, so return ok.)
+ */
+ error = nfsrv_getlockfh(vp, NFSLCK_CHECK, NULL, &nfh, p);
+ NFSLOCKSTATE();
+ if (!error)
+ error = nfsrv_getlockfile(NFSLCK_CHECK, NULL, &lfp, &nfh);
+ if (error) {
+ NFSUNLOCKSTATE();
+ if (error == -1)
+ return (0);
+ return (error);
+ }
+
+ /*
+ * Now, look for a write delegation.
+ */
+ LIST_FOREACH(stp, &lfp->lf_deleg, ls_file) {
+ if (stp->ls_flags & NFSLCK_DELEGWRITE)
+ break;
+ }
+ if (stp == LIST_END(&lfp->lf_deleg)) {
+ NFSUNLOCKSTATE();
+ return (0);
+ }
+ clp = stp->ls_clp;
+ delegfilerev = stp->ls_filerev;
+
+ /*
+ * If the Write delegation was issued as a part of this Compound RPC
+ * or if we have an Implied Clientid (used in a previous Op in this
+ * compound) and it is the client the delegation was issued to,
+ * just return ok.
+ * I also assume that it is from the same client iff the network
+ * host IP address is the same as the callback address. (Not
+ * exactly correct by the RFC, but avoids a lot of Getattr
+ * callbacks.)
+ */
+ if (nd->nd_compref == stp->ls_compref ||
+ ((nd->nd_flag & ND_IMPLIEDCLID) &&
+ clp->lc_clientid.qval == nd->nd_clientid.qval) ||
+ nfsaddr2_match(clp->lc_req.nr_nam, nd->nd_nam)) {
+ NFSUNLOCKSTATE();
+ return (0);
+ }
+
+ /*
+ * We are now done with the delegation state structure,
+ * so the statelock can be released and we can now tsleep().
+ */
+
+ /*
+ * Now, we must do the CB Getattr callback, to see if Change or Size
+ * has changed.
+ */
+ if (clp->lc_expiry >= NFSD_MONOSEC) {
+ NFSUNLOCKSTATE();
+ NFSVNO_ATTRINIT(&nva);
+ nva.na_filerev = NFS64BITSSET;
+ error = nfsrv_docallback(clp, NFSV4OP_CBGETATTR, NULL,
+ 0, &nfh, &nva, &cbbits, p);
+ if (!error) {
+ if ((nva.na_filerev != NFS64BITSSET &&
+ nva.na_filerev > delegfilerev) ||
+ (NFSVNO_ISSETSIZE(&nva) &&
+ nva.na_size != nvap->na_size)) {
+ nfsvno_updfilerev(vp, nvap, cred, p);
+ if (NFSVNO_ISSETSIZE(&nva))
+ nvap->na_size = nva.na_size;
+ }
+ }
+ } else {
+ NFSUNLOCKSTATE();
+ }
+ return (0);
+}
+
+/*
+ * This function looks for openowners that haven't had any opens for
+ * a while and throws them away. Called by an nfsd when NFSNSF_NOOPENS
+ * is set.
+ */
+APPLESTATIC void
+nfsrv_throwawayopens(NFSPROC_T *p)
+{
+ struct nfsclient *clp, *nclp;
+ struct nfsstate *stp, *nstp;
+ int i;
+
+ NFSLOCKSTATE();
+ nfsrv_stablefirst.nsf_flags &= ~NFSNSF_NOOPENS;
+ /*
+ * For each client...
+ */
+ for (i = 0; i < NFSCLIENTHASHSIZE; i++) {
+ LIST_FOREACH_SAFE(clp, &nfsclienthash[i], lc_hash, nclp) {
+ LIST_FOREACH_SAFE(stp, &clp->lc_open, ls_list, nstp) {
+ if (LIST_EMPTY(&stp->ls_open) &&
+ (stp->ls_noopens > NFSNOOPEN ||
+ (nfsrv_openpluslock * 2) >
+ NFSRV_V4STATELIMIT))
+ nfsrv_freeopenowner(stp, 0, p);
+ }
+ }
+ }
+ NFSUNLOCKSTATE();
+}
+
+/*
+ * This function checks to see if the credentials are the same.
+ * Returns 1 for not same, 0 otherwise.
+ */
+static int
+nfsrv_notsamecredname(struct nfsrv_descript *nd, struct nfsclient *clp)
+{
+
+ if (nd->nd_flag & ND_GSS) {
+ if (!(clp->lc_flags & LCL_GSS))
+ return (1);
+ if (clp->lc_flags & LCL_NAME) {
+ if (nd->nd_princlen != clp->lc_namelen ||
+ NFSBCMP(nd->nd_principal, clp->lc_name,
+ clp->lc_namelen))
+ return (1);
+ else
+ return (0);
+ }
+ if (nd->nd_cred->cr_uid == clp->lc_uid)
+ return (0);
+ else
+ return (1);
+ } else if (clp->lc_flags & LCL_GSS)
+ return (1);
+ /*
+ * For AUTH_SYS, allow the same uid or root. (This is underspecified
+ * in RFC3530, which talks about principals, but doesn't say anything
+ * about uids for AUTH_SYS.)
+ */
+ if (nd->nd_cred->cr_uid == clp->lc_uid || nd->nd_cred->cr_uid == 0)
+ return (0);
+ else
+ return (1);
+}
+
+/*
+ * Calculate the lease expiry time.
+ */
+static time_t
+nfsrv_leaseexpiry(void)
+{
+ struct timeval curtime;
+
+ NFSGETTIME(&curtime);
+ if (nfsrv_stablefirst.nsf_eograce > NFSD_MONOSEC)
+ return (NFSD_MONOSEC + 2 * (nfsrv_lease + NFSRV_LEASEDELTA));
+ return (NFSD_MONOSEC + nfsrv_lease + NFSRV_LEASEDELTA);
+}
+
+/*
+ * Delay the delegation timeout as far as ls_delegtimelimit, as required.
+ */
+static void
+nfsrv_delaydelegtimeout(struct nfsstate *stp)
+{
+
+ if ((stp->ls_flags & NFSLCK_DELEGRECALL) == 0)
+ return;
+
+ if ((stp->ls_delegtime + 15) > NFSD_MONOSEC &&
+ stp->ls_delegtime < stp->ls_delegtimelimit) {
+ stp->ls_delegtime += nfsrv_lease;
+ if (stp->ls_delegtime > stp->ls_delegtimelimit)
+ stp->ls_delegtime = stp->ls_delegtimelimit;
+ }
+}
+
+/*
+ * Go through a lock list and set local locks for all ranges.
+ * This assumes that the lock list is sorted on increasing
+ * lo_first and that the list won't change, despite the possibility
+ * of sleeps.
+ */
+static void
+nfsrv_locallocks(vnode_t vp, struct nfslockfile *lfp,
+ NFSPROC_T *p)
+{
+ struct nfslock *lop, *nlop;
+ vnode_t tvp;
+ int newcollate, flags = 0;
+ u_int64_t first = 0x0ull, end = 0x0ull;
+
+ if (!nfsrv_dolocallocks)
+ return;
+ /*
+ * If vp is NULL, a vnode must be aquired from the file
+ * handle.
+ */
+ if (vp == NULL) {
+ if (lfp == NULL)
+ panic("nfsrv_locallocks");
+ tvp = nfsvno_getvp(&lfp->lf_fh);
+ if (tvp == NULL)
+ return;
+ } else {
+ tvp = vp;
+ }
+
+ /*
+ * If lfp == NULL, the lock list is empty, so just unlock
+ * everything.
+ */
+ if (lfp == NULL) {
+ (void) nfsvno_advlock(tvp, F_UNLCK, (u_int64_t)0,
+ NFS64BITSSET, p);
+ /* vp can't be NULL */
+ return;
+ }
+
+ /* handle whole file case first */
+ lop = LIST_FIRST(&lfp->lf_lock);
+ if (lop != LIST_END(&lfp->lf_lock) &&
+ lop->lo_first == (u_int64_t)0 &&
+ lop->lo_end == NFS64BITSSET) {
+ if (lop->lo_flags & NFSLCK_WRITE)
+ (void) nfsvno_advlock(tvp, F_WRLCK, lop->lo_first,
+ lop->lo_end, p);
+ else
+ (void) nfsvno_advlock(tvp, F_RDLCK, lop->lo_first,
+ lop->lo_end, p);
+ if (vp == NULL)
+ vput(tvp);
+ return;
+ }
+
+ /*
+ * Now, handle the separate byte ranges cases.
+ */
+ (void) nfsvno_advlock(tvp, F_UNLCK, (u_int64_t)0,
+ NFS64BITSSET, p);
+ newcollate = 1;
+ while (lop != LIST_END(&lfp->lf_lock)) {
+ nlop = LIST_NEXT(lop, lo_lckfile);
+ if (newcollate) {
+ first = lop->lo_first;
+ end = lop->lo_end;
+ flags = lop->lo_flags;
+ newcollate = 0;
+ }
+ if (nlop != LIST_END(&lfp->lf_lock) &&
+ flags == nlop->lo_flags &&
+ end >= nlop->lo_first) {
+ /* can collate this one */
+ end = nlop->lo_end;
+ } else {
+ /* do the local lock and start again */
+ if (flags & NFSLCK_WRITE)
+ (void) nfsvno_advlock(tvp, F_WRLCK, first,
+ end, p);
+ else
+ (void) nfsvno_advlock(tvp, F_RDLCK, first,
+ end, p);
+ newcollate = 1;
+ }
+ lop = nlop;
+ }
+ if (vp == NULL)
+ vput(tvp);
+}
+
+/*
+ * This function checks to see if there is any other state associated
+ * with the openowner for this Open.
+ * It returns 1 if there is no other state, 0 otherwise.
+ */
+static int
+nfsrv_nootherstate(struct nfsstate *stp)
+{
+ struct nfsstate *tstp;
+
+ LIST_FOREACH(tstp, &stp->ls_openowner->ls_open, ls_list) {
+ if (tstp != stp || !LIST_EMPTY(&tstp->ls_lock))
+ return (0);
+ }
+ return (1);
+}
+
diff --git a/sys/fs/nfsserver/nfs_nfsdsubs.c b/sys/fs/nfsserver/nfs_nfsdsubs.c
new file mode 100644
index 0000000..51f367a
--- /dev/null
+++ b/sys/fs/nfsserver/nfs_nfsdsubs.c
@@ -0,0 +1,2021 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#ifndef APPLEKEXT
+/*
+ * These functions support the macros and help fiddle mbuf chains for
+ * the nfs op functions. They do things like create the rpc header and
+ * copy data between mbuf chains and uio lists.
+ */
+#include <fs/nfs/nfsport.h>
+
+extern u_int32_t newnfs_true, newnfs_false;
+extern int nfs_pubfhset;
+extern struct nfsclienthashhead nfsclienthash[NFSCLIENTHASHSIZE];
+extern struct nfslockhashhead nfslockhash[NFSLOCKHASHSIZE];
+extern int nfsrv_useacl;
+extern uid_t nfsrv_defaultuid;
+extern gid_t nfsrv_defaultgid;
+
+char nfs_v2pubfh[NFSX_V2FH];
+static nfstype newnfsv2_type[9] = { NFNON, NFREG, NFDIR, NFBLK, NFCHR, NFLNK,
+ NFNON, NFCHR, NFNON };
+extern nfstype nfsv34_type[9];
+#endif /* !APPLEKEXT */
+
+static char nfsrv_hexdigit(char, int *);
+
+/*
+ * Maps errno values to nfs error numbers.
+ * Use NFSERR_IO as the catch all for ones not specifically defined in
+ * RFC 1094.
+ */
+static u_char nfsrv_v2errmap[ELAST] = {
+ NFSERR_PERM, NFSERR_NOENT, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_NXIO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_ACCES, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_EXIST, NFSERR_IO, NFSERR_NODEV, NFSERR_NOTDIR,
+ NFSERR_ISDIR, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_FBIG, NFSERR_NOSPC, NFSERR_IO, NFSERR_ROFS,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_NAMETOL, NFSERR_IO, NFSERR_IO,
+ NFSERR_NOTEMPTY, NFSERR_IO, NFSERR_IO, NFSERR_DQUOT, NFSERR_STALE,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO, NFSERR_IO,
+ NFSERR_IO,
+};
+
+/*
+ * Maps errno values to nfs error numbers.
+ * Although it is not obvious whether or not NFS clients really care if
+ * a returned error value is in the specified list for the procedure, the
+ * safest thing to do is filter them appropriately. For Version 2, the
+ * X/Open XNFS document is the only specification that defines error values
+ * for each RPC (The RFC simply lists all possible error values for all RPCs),
+ * so I have decided to not do this for Version 2.
+ * The first entry is the default error return and the rest are the valid
+ * errors for that RPC in increasing numeric order.
+ */
+static short nfsv3err_null[] = {
+ 0,
+ 0,
+};
+
+static short nfsv3err_getattr[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_setattr[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_PERM,
+ NFSERR_IO,
+ NFSERR_INVAL,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOT_SYNC,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_lookup[] = {
+ NFSERR_IO,
+ NFSERR_NOENT,
+ NFSERR_ACCES,
+ NFSERR_NAMETOL,
+ NFSERR_IO,
+ NFSERR_NOTDIR,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_access[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_readlink[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_INVAL,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_read[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_NXIO,
+ NFSERR_ACCES,
+ NFSERR_INVAL,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_write[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_NOSPC,
+ NFSERR_INVAL,
+ NFSERR_FBIG,
+ NFSERR_ROFS,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_create[] = {
+ NFSERR_IO,
+ NFSERR_EXIST,
+ NFSERR_NAMETOL,
+ NFSERR_ACCES,
+ NFSERR_IO,
+ NFSERR_NOTDIR,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_mkdir[] = {
+ NFSERR_IO,
+ NFSERR_EXIST,
+ NFSERR_ACCES,
+ NFSERR_NAMETOL,
+ NFSERR_IO,
+ NFSERR_NOTDIR,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_symlink[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_NAMETOL,
+ NFSERR_NOSPC,
+ NFSERR_IO,
+ NFSERR_NOTDIR,
+ NFSERR_ROFS,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_mknod[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_NAMETOL,
+ NFSERR_NOSPC,
+ NFSERR_IO,
+ NFSERR_NOTDIR,
+ NFSERR_ROFS,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ NFSERR_BADTYPE,
+ 0,
+};
+
+static short nfsv3err_remove[] = {
+ NFSERR_IO,
+ NFSERR_NOENT,
+ NFSERR_ACCES,
+ NFSERR_NAMETOL,
+ NFSERR_IO,
+ NFSERR_NOTDIR,
+ NFSERR_ROFS,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_rmdir[] = {
+ NFSERR_IO,
+ NFSERR_NOENT,
+ NFSERR_ACCES,
+ NFSERR_NOTDIR,
+ NFSERR_NAMETOL,
+ NFSERR_IO,
+ NFSERR_EXIST,
+ NFSERR_INVAL,
+ NFSERR_ROFS,
+ NFSERR_NOTEMPTY,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_rename[] = {
+ NFSERR_IO,
+ NFSERR_NOENT,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_NAMETOL,
+ NFSERR_XDEV,
+ NFSERR_IO,
+ NFSERR_NOTDIR,
+ NFSERR_ISDIR,
+ NFSERR_INVAL,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_MLINK,
+ NFSERR_NOTEMPTY,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_link[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_EXIST,
+ NFSERR_NAMETOL,
+ NFSERR_IO,
+ NFSERR_XDEV,
+ NFSERR_NOTDIR,
+ NFSERR_INVAL,
+ NFSERR_NOSPC,
+ NFSERR_ROFS,
+ NFSERR_MLINK,
+ NFSERR_DQUOT,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_readdir[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_NOTDIR,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_BAD_COOKIE,
+ NFSERR_TOOSMALL,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_readdirplus[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_NOTDIR,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_BAD_COOKIE,
+ NFSERR_NOTSUPP,
+ NFSERR_TOOSMALL,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_fsstat[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_fsinfo[] = {
+ NFSERR_STALE,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_pathconf[] = {
+ NFSERR_STALE,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short nfsv3err_commit[] = {
+ NFSERR_IO,
+ NFSERR_IO,
+ NFSERR_STALE,
+ NFSERR_BADHANDLE,
+ NFSERR_SERVERFAULT,
+ NFSERR_DELAY,
+ 0,
+};
+
+static short *nfsrv_v3errmap[] = {
+ nfsv3err_null,
+ nfsv3err_getattr,
+ nfsv3err_setattr,
+ nfsv3err_lookup,
+ nfsv3err_access,
+ nfsv3err_readlink,
+ nfsv3err_read,
+ nfsv3err_write,
+ nfsv3err_create,
+ nfsv3err_mkdir,
+ nfsv3err_symlink,
+ nfsv3err_mknod,
+ nfsv3err_remove,
+ nfsv3err_rmdir,
+ nfsv3err_rename,
+ nfsv3err_link,
+ nfsv3err_readdir,
+ nfsv3err_readdirplus,
+ nfsv3err_fsstat,
+ nfsv3err_fsinfo,
+ nfsv3err_pathconf,
+ nfsv3err_commit,
+};
+
+/*
+ * And the same for V4.
+ */
+static short nfsv4err_null[] = {
+ 0,
+ 0,
+};
+
+static short nfsv4err_access[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADHANDLE,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_close[] = {
+ NFSERR_EXPIRED,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADHANDLE,
+ NFSERR_BADSEQID,
+ NFSERR_BADSTATEID,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_EXPIRED,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_ISDIR,
+ NFSERR_LEASEMOVED,
+ NFSERR_LOCKSHELD,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_OLDSTATEID,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALESTATEID,
+ 0,
+};
+
+static short nfsv4err_commit[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADHANDLE,
+ NFSERR_BADXDR,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_ISDIR,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_RESOURCE,
+ NFSERR_ROFS,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_create[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_ATTRNOTSUPP,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADNAME,
+ NFSERR_BADOWNER,
+ NFSERR_BADTYPE,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_DQUOT,
+ NFSERR_EXIST,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NAMETOL,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOSPC,
+ NFSERR_NOTDIR,
+ NFSERR_PERM,
+ NFSERR_RESOURCE,
+ NFSERR_ROFS,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_delegpurge[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_BADXDR,
+ NFSERR_NOTSUPP,
+ NFSERR_LEASEMOVED,
+ NFSERR_MOVED,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALECLIENTID,
+ 0,
+};
+
+static short nfsv4err_delegreturn[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADSTATEID,
+ NFSERR_BADXDR,
+ NFSERR_EXPIRED,
+ NFSERR_INVAL,
+ NFSERR_LEASEMOVED,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_OLDSTATEID,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALESTATEID,
+ 0,
+};
+
+static short nfsv4err_getattr[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADHANDLE,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_getfh[] = {
+ NFSERR_BADHANDLE,
+ NFSERR_BADHANDLE,
+ NFSERR_FHEXPIRED,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_link[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADNAME,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_DQUOT,
+ NFSERR_EXIST,
+ NFSERR_FHEXPIRED,
+ NFSERR_FILEOPEN,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_ISDIR,
+ NFSERR_MLINK,
+ NFSERR_MOVED,
+ NFSERR_NAMETOL,
+ NFSERR_NOENT,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOSPC,
+ NFSERR_NOTDIR,
+ NFSERR_NOTSUPP,
+ NFSERR_RESOURCE,
+ NFSERR_ROFS,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_WRONGSEC,
+ NFSERR_XDEV,
+ 0,
+};
+
+static short nfsv4err_lock[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ACCES,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADHANDLE,
+ NFSERR_BADRANGE,
+ NFSERR_BADSEQID,
+ NFSERR_BADSTATEID,
+ NFSERR_BADXDR,
+ NFSERR_DEADLOCK,
+ NFSERR_DELAY,
+ NFSERR_DENIED,
+ NFSERR_EXPIRED,
+ NFSERR_FHEXPIRED,
+ NFSERR_GRACE,
+ NFSERR_INVAL,
+ NFSERR_ISDIR,
+ NFSERR_LEASEMOVED,
+ NFSERR_LOCKNOTSUPP,
+ NFSERR_LOCKRANGE,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOGRACE,
+ NFSERR_OLDSTATEID,
+ NFSERR_OPENMODE,
+ NFSERR_RECLAIMBAD,
+ NFSERR_RECLAIMCONFLICT,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALECLIENTID,
+ NFSERR_STALESTATEID,
+ 0,
+};
+
+static short nfsv4err_lockt[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ACCES,
+ NFSERR_BADHANDLE,
+ NFSERR_BADRANGE,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_DENIED,
+ NFSERR_FHEXPIRED,
+ NFSERR_GRACE,
+ NFSERR_INVAL,
+ NFSERR_ISDIR,
+ NFSERR_LEASEMOVED,
+ NFSERR_LOCKRANGE,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALECLIENTID,
+ 0,
+};
+
+static short nfsv4err_locku[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ACCES,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADHANDLE,
+ NFSERR_BADRANGE,
+ NFSERR_BADSEQID,
+ NFSERR_BADSTATEID,
+ NFSERR_BADXDR,
+ NFSERR_EXPIRED,
+ NFSERR_FHEXPIRED,
+ NFSERR_GRACE,
+ NFSERR_INVAL,
+ NFSERR_ISDIR,
+ NFSERR_LEASEMOVED,
+ NFSERR_LOCKRANGE,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_OLDSTATEID,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALESTATEID,
+ 0,
+};
+
+static short nfsv4err_lookup[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADNAME,
+ NFSERR_BADXDR,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NAMETOL,
+ NFSERR_NOENT,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOTDIR,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_SYMLINK,
+ NFSERR_WRONGSEC,
+ 0,
+};
+
+static short nfsv4err_lookupp[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADHANDLE,
+ NFSERR_FHEXPIRED,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NOENT,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOTDIR,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_nverify[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_ATTRNOTSUPP,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_RESOURCE,
+ NFSERR_SAME,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_open[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_ADMINREVOKED,
+ NFSERR_ATTRNOTSUPP,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADNAME,
+ NFSERR_BADOWNER,
+ NFSERR_BADSEQID,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_DQUOT,
+ NFSERR_EXIST,
+ NFSERR_EXPIRED,
+ NFSERR_FHEXPIRED,
+ NFSERR_GRACE,
+ NFSERR_IO,
+ NFSERR_INVAL,
+ NFSERR_ISDIR,
+ NFSERR_LEASEMOVED,
+ NFSERR_MOVED,
+ NFSERR_NAMETOL,
+ NFSERR_NOENT,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOGRACE,
+ NFSERR_NOSPC,
+ NFSERR_NOTDIR,
+ NFSERR_NOTSUPP,
+ NFSERR_PERM,
+ NFSERR_RECLAIMBAD,
+ NFSERR_RECLAIMCONFLICT,
+ NFSERR_RESOURCE,
+ NFSERR_ROFS,
+ NFSERR_SERVERFAULT,
+ NFSERR_SHAREDENIED,
+ NFSERR_STALE,
+ NFSERR_STALECLIENTID,
+ NFSERR_SYMLINK,
+ NFSERR_WRONGSEC,
+ 0,
+};
+
+static short nfsv4err_openattr[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADHANDLE,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_DQUOT,
+ NFSERR_FHEXPIRED,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NOENT,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOSPC,
+ NFSERR_NOTSUPP,
+ NFSERR_RESOURCE,
+ NFSERR_ROFS,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_openconfirm[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADHANDLE,
+ NFSERR_BADSEQID,
+ NFSERR_BADSTATEID,
+ NFSERR_BADXDR,
+ NFSERR_EXPIRED,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_ISDIR,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_OLDSTATEID,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALESTATEID,
+ 0,
+};
+
+static short nfsv4err_opendowngrade[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADHANDLE,
+ NFSERR_BADSEQID,
+ NFSERR_BADSTATEID,
+ NFSERR_BADXDR,
+ NFSERR_EXPIRED,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_OLDSTATEID,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALESTATEID,
+ 0,
+};
+
+static short nfsv4err_putfh[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_BADHANDLE,
+ NFSERR_BADXDR,
+ NFSERR_FHEXPIRED,
+ NFSERR_MOVED,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_WRONGSEC,
+ 0,
+};
+
+static short nfsv4err_putpubfh[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_WRONGSEC,
+ 0,
+};
+
+static short nfsv4err_putrootfh[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_WRONGSEC,
+ 0,
+};
+
+static short nfsv4err_read[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADHANDLE,
+ NFSERR_BADSTATEID,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_EXPIRED,
+ NFSERR_FHEXPIRED,
+ NFSERR_GRACE,
+ NFSERR_IO,
+ NFSERR_INVAL,
+ NFSERR_ISDIR,
+ NFSERR_LEASEMOVED,
+ NFSERR_LOCKED,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NXIO,
+ NFSERR_OLDSTATEID,
+ NFSERR_OPENMODE,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALESTATEID,
+ 0,
+};
+
+static short nfsv4err_readdir[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADHANDLE,
+ NFSERR_BAD_COOKIE,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOTDIR,
+ NFSERR_NOTSAME,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_TOOSMALL,
+ 0,
+};
+
+static short nfsv4err_readlink[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADHANDLE,
+ NFSERR_DELAY,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_ISDIR,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOTSUPP,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_remove[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADNAME,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_FHEXPIRED,
+ NFSERR_FILEOPEN,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NAMETOL,
+ NFSERR_NOENT,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOTDIR,
+ NFSERR_NOTEMPTY,
+ NFSERR_RESOURCE,
+ NFSERR_ROFS,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_rename[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADNAME,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_DQUOT,
+ NFSERR_EXIST,
+ NFSERR_FHEXPIRED,
+ NFSERR_FILEOPEN,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_MOVED,
+ NFSERR_NAMETOL,
+ NFSERR_NOENT,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOSPC,
+ NFSERR_NOTDIR,
+ NFSERR_NOTEMPTY,
+ NFSERR_RESOURCE,
+ NFSERR_ROFS,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_WRONGSEC,
+ NFSERR_XDEV,
+ 0,
+};
+
+static short nfsv4err_renew[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ACCES,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADXDR,
+ NFSERR_CBPATHDOWN,
+ NFSERR_EXPIRED,
+ NFSERR_LEASEMOVED,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALECLIENTID,
+ 0,
+};
+
+static short nfsv4err_restorefh[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_BADHANDLE,
+ NFSERR_FHEXPIRED,
+ NFSERR_MOVED,
+ NFSERR_RESOURCE,
+ NFSERR_RESTOREFH,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_WRONGSEC,
+ 0,
+};
+
+static short nfsv4err_savefh[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_BADHANDLE,
+ NFSERR_FHEXPIRED,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_secinfo[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ACCES,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADNAME,
+ NFSERR_BADXDR,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_MOVED,
+ NFSERR_NAMETOL,
+ NFSERR_NOENT,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOTDIR,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_setattr[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_ADMINREVOKED,
+ NFSERR_ATTRNOTSUPP,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADOWNER,
+ NFSERR_BADSTATEID,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_DQUOT,
+ NFSERR_EXPIRED,
+ NFSERR_FBIG,
+ NFSERR_FHEXPIRED,
+ NFSERR_GRACE,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_ISDIR,
+ NFSERR_LOCKED,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOSPC,
+ NFSERR_OLDSTATEID,
+ NFSERR_OPENMODE,
+ NFSERR_PERM,
+ NFSERR_RESOURCE,
+ NFSERR_ROFS,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALESTATEID,
+ 0,
+};
+
+static short nfsv4err_setclientid[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_BADXDR,
+ NFSERR_CLIDINUSE,
+ NFSERR_INVAL,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ 0,
+};
+
+static short nfsv4err_setclientidconfirm[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_BADXDR,
+ NFSERR_CLIDINUSE,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALECLIENTID,
+ 0,
+};
+
+static short nfsv4err_verify[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ACCES,
+ NFSERR_ATTRNOTSUPP,
+ NFSERR_BADCHAR,
+ NFSERR_BADHANDLE,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_FHEXPIRED,
+ NFSERR_INVAL,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOTSAME,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ 0,
+};
+
+static short nfsv4err_write[] = {
+ NFSERR_IO,
+ NFSERR_ACCES,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADHANDLE,
+ NFSERR_BADSTATEID,
+ NFSERR_BADXDR,
+ NFSERR_DELAY,
+ NFSERR_DQUOT,
+ NFSERR_EXPIRED,
+ NFSERR_FBIG,
+ NFSERR_FHEXPIRED,
+ NFSERR_GRACE,
+ NFSERR_INVAL,
+ NFSERR_IO,
+ NFSERR_ISDIR,
+ NFSERR_LEASEMOVED,
+ NFSERR_LOCKED,
+ NFSERR_MOVED,
+ NFSERR_NOFILEHANDLE,
+ NFSERR_NOSPC,
+ NFSERR_NXIO,
+ NFSERR_OLDSTATEID,
+ NFSERR_OPENMODE,
+ NFSERR_RESOURCE,
+ NFSERR_ROFS,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALE,
+ NFSERR_STALESTATEID,
+ 0,
+};
+
+static short nfsv4err_releaselockowner[] = {
+ NFSERR_SERVERFAULT,
+ NFSERR_ADMINREVOKED,
+ NFSERR_BADXDR,
+ NFSERR_EXPIRED,
+ NFSERR_LEASEMOVED,
+ NFSERR_LOCKSHELD,
+ NFSERR_RESOURCE,
+ NFSERR_SERVERFAULT,
+ NFSERR_STALECLIENTID,
+ 0,
+};
+
+static short *nfsrv_v4errmap[] = {
+ nfsv4err_null,
+ nfsv4err_null,
+ nfsv4err_null,
+ nfsv4err_access,
+ nfsv4err_close,
+ nfsv4err_commit,
+ nfsv4err_create,
+ nfsv4err_delegpurge,
+ nfsv4err_delegreturn,
+ nfsv4err_getattr,
+ nfsv4err_getfh,
+ nfsv4err_link,
+ nfsv4err_lock,
+ nfsv4err_lockt,
+ nfsv4err_locku,
+ nfsv4err_lookup,
+ nfsv4err_lookupp,
+ nfsv4err_nverify,
+ nfsv4err_open,
+ nfsv4err_openattr,
+ nfsv4err_openconfirm,
+ nfsv4err_opendowngrade,
+ nfsv4err_putfh,
+ nfsv4err_putpubfh,
+ nfsv4err_putrootfh,
+ nfsv4err_read,
+ nfsv4err_readdir,
+ nfsv4err_readlink,
+ nfsv4err_remove,
+ nfsv4err_rename,
+ nfsv4err_renew,
+ nfsv4err_restorefh,
+ nfsv4err_savefh,
+ nfsv4err_secinfo,
+ nfsv4err_setattr,
+ nfsv4err_setclientid,
+ nfsv4err_setclientidconfirm,
+ nfsv4err_verify,
+ nfsv4err_write,
+ nfsv4err_releaselockowner,
+};
+
+/*
+ * A fiddled version of m_adj() that ensures null fill to a long
+ * boundary and only trims off the back end
+ */
+APPLESTATIC void
+nfsrv_adj(mbuf_t mp, int len, int nul)
+{
+ mbuf_t m;
+ int count, i;
+ char *cp;
+
+ /*
+ * Trim from tail. Scan the mbuf chain,
+ * calculating its length and finding the last mbuf.
+ * If the adjustment only affects this mbuf, then just
+ * adjust and return. Otherwise, rescan and truncate
+ * after the remaining size.
+ */
+ count = 0;
+ m = mp;
+ for (;;) {
+ count += mbuf_len(m);
+ if (mbuf_next(m) == NULL)
+ break;
+ m = mbuf_next(m);
+ }
+ if (mbuf_len(m) > len) {
+ mbuf_setlen(m, mbuf_len(m) - len);
+ if (nul > 0) {
+ cp = NFSMTOD(m, caddr_t) + mbuf_len(m) - nul;
+ for (i = 0; i < nul; i++)
+ *cp++ = '\0';
+ }
+ return;
+ }
+ count -= len;
+ if (count < 0)
+ count = 0;
+ /*
+ * Correct length for chain is "count".
+ * Find the mbuf with last data, adjust its length,
+ * and toss data from remaining mbufs on chain.
+ */
+ for (m = mp; m; m = mbuf_next(m)) {
+ if (mbuf_len(m) >= count) {
+ mbuf_setlen(m, count);
+ if (nul > 0) {
+ cp = NFSMTOD(m, caddr_t) + mbuf_len(m) - nul;
+ for (i = 0; i < nul; i++)
+ *cp++ = '\0';
+ }
+ break;
+ }
+ count -= mbuf_len(m);
+ }
+ for (m = mbuf_next(m); m; m = mbuf_next(m))
+ mbuf_setlen(m, 0);
+}
+
+/*
+ * Make these functions instead of macros, so that the kernel text size
+ * doesn't get too big...
+ */
+APPLESTATIC void
+nfsrv_wcc(struct nfsrv_descript *nd, int before_ret,
+ struct nfsvattr *before_nvap, int after_ret, struct nfsvattr *after_nvap)
+{
+ u_int32_t *tl;
+
+ if (before_ret) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl = newnfs_false;
+ } else {
+ NFSM_BUILD(tl, u_int32_t *, 7 * NFSX_UNSIGNED);
+ *tl++ = newnfs_true;
+ txdr_hyper(before_nvap->na_size, tl);
+ tl += 2;
+ txdr_nfsv3time(&(before_nvap->na_mtime), tl);
+ tl += 2;
+ txdr_nfsv3time(&(before_nvap->na_ctime), tl);
+ }
+ nfsrv_postopattr(nd, after_ret, after_nvap);
+}
+
+APPLESTATIC void
+nfsrv_postopattr(struct nfsrv_descript *nd, int after_ret,
+ struct nfsvattr *after_nvap)
+{
+ u_int32_t *tl;
+
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (after_ret)
+ *tl = newnfs_false;
+ else {
+ *tl = newnfs_true;
+ nfsrv_fillattr(nd, after_nvap);
+ }
+}
+
+/*
+ * Fill in file attributes for V2 and 3. For V4, call a separate
+ * routine that sifts through all the attribute bits.
+ */
+APPLESTATIC void
+nfsrv_fillattr(struct nfsrv_descript *nd, struct nfsvattr *nvap)
+{
+ struct nfs_fattr *fp;
+ int fattr_size;
+
+ /*
+ * Build space for the attribute structure.
+ */
+ if (nd->nd_flag & ND_NFSV3)
+ fattr_size = NFSX_V3FATTR;
+ else
+ fattr_size = NFSX_V2FATTR;
+ NFSM_BUILD(fp, struct nfs_fattr *, fattr_size);
+
+ /*
+ * Now just fill it all in.
+ */
+ fp->fa_nlink = txdr_unsigned(nvap->na_nlink);
+ fp->fa_uid = txdr_unsigned(nvap->na_uid);
+ fp->fa_gid = txdr_unsigned(nvap->na_gid);
+ if (nd->nd_flag & ND_NFSV3) {
+ fp->fa_type = vtonfsv34_type(nvap->na_type);
+ fp->fa_mode = vtonfsv34_mode(nvap->na_mode);
+ txdr_hyper(nvap->na_size, &fp->fa3_size);
+ txdr_hyper(nvap->na_bytes, &fp->fa3_used);
+ fp->fa3_rdev.specdata1 = txdr_unsigned(NFSMAJOR(nvap->na_rdev));
+ fp->fa3_rdev.specdata2 = txdr_unsigned(NFSMINOR(nvap->na_rdev));
+ fp->fa3_fsid.nfsuquad[0] = 0;
+ fp->fa3_fsid.nfsuquad[1] = txdr_unsigned(nvap->na_fsid);
+ fp->fa3_fileid.nfsuquad[0] = 0;
+ fp->fa3_fileid.nfsuquad[1] = txdr_unsigned(nvap->na_fileid);
+ txdr_nfsv3time(&nvap->na_atime, &fp->fa3_atime);
+ txdr_nfsv3time(&nvap->na_mtime, &fp->fa3_mtime);
+ txdr_nfsv3time(&nvap->na_ctime, &fp->fa3_ctime);
+ } else {
+ fp->fa_type = vtonfsv2_type(nvap->na_type);
+ fp->fa_mode = vtonfsv2_mode(nvap->na_type, nvap->na_mode);
+ fp->fa2_size = txdr_unsigned(nvap->na_size);
+ fp->fa2_blocksize = txdr_unsigned(nvap->na_blocksize);
+ if (nvap->na_type == VFIFO)
+ fp->fa2_rdev = 0xffffffff;
+ else
+ fp->fa2_rdev = txdr_unsigned(nvap->na_rdev);
+ fp->fa2_blocks = txdr_unsigned(nvap->na_bytes / NFS_FABLKSIZE);
+ fp->fa2_fsid = txdr_unsigned(nvap->na_fsid);
+ fp->fa2_fileid = txdr_unsigned(nvap->na_fileid);
+ txdr_nfsv2time(&nvap->na_atime, &fp->fa2_atime);
+ txdr_nfsv2time(&nvap->na_mtime, &fp->fa2_mtime);
+ txdr_nfsv2time(&nvap->na_ctime, &fp->fa2_ctime);
+ }
+}
+
+/*
+ * This function gets a file handle out of an mbuf list.
+ * It returns 0 for success, EBADRPC otherwise.
+ * If sets the third flagp argument to 1 if the file handle is
+ * the public file handle.
+ * For NFSv4, if the length is incorrect, set nd_repstat == NFSERR_BADHANDLE
+ */
+APPLESTATIC int
+nfsrv_mtofh(struct nfsrv_descript *nd, struct nfsrvfh *fhp)
+{
+ u_int32_t *tl;
+ int error = 0, len, copylen;
+
+ if (nd->nd_flag & (ND_NFSV3 | ND_NFSV4)) {
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ len = fxdr_unsigned(int, *tl);
+ if (len == 0 && nfs_pubfhset && (nd->nd_flag & ND_NFSV3) &&
+ nd->nd_procnum == NFSPROC_LOOKUP) {
+ nd->nd_flag |= ND_PUBLOOKUP;
+ return (0);
+ }
+ if (len < NFSRV_MINFH || len > NFSRV_MAXFH) {
+ if (nd->nd_flag & ND_NFSV4) {
+ if (len > 0 && len <= NFSX_V4FHMAX) {
+ error = nfsm_advance(nd, NFSM_RNDUP(len), -1);
+ if (error)
+ return (error);
+ nd->nd_repstat = NFSERR_BADHANDLE;
+ return (0);
+ } else {
+ return (EBADRPC);
+ }
+ } else {
+ return (EBADRPC);
+ }
+ }
+ copylen = len;
+ } else {
+ /*
+ * For NFSv2, the file handle is always 32 bytes on the
+ * wire, but this server only cares about the first
+ * NFSRV_MAXFH bytes.
+ */
+ len = NFSX_V2FH;
+ copylen = NFSRV_MAXFH;
+ }
+ NFSM_DISSECT(tl, u_int32_t *, len);
+ if ((nd->nd_flag & ND_NFSV2) && nfs_pubfhset &&
+ nd->nd_procnum == NFSPROC_LOOKUP &&
+ !NFSBCMP((caddr_t)tl, nfs_v2pubfh, NFSX_V2FH)) {
+ nd->nd_flag |= ND_PUBLOOKUP;
+ return (0);
+ }
+ NFSBCOPY(tl, (caddr_t)fhp->nfsrvfh_data, copylen);
+ fhp->nfsrvfh_len = copylen;
+nfsmout:
+ return (error);
+}
+
+/*
+ * Map errnos to NFS error numbers. For Version 3 and 4 also filter out error
+ * numbers not specified for the associated procedure.
+ * NFSPROC_NOOP is a special case, where the high order bits of nd_repstat
+ * should be cleared. NFSPROC_NOOP is used to return errors when a valid
+ * RPC procedure is not involved.
+ * Returns the error number in XDR.
+ */
+APPLESTATIC int
+nfsd_errmap(struct nfsrv_descript *nd)
+{
+ short *defaulterrp, *errp;
+
+ if (!nd->nd_repstat)
+ return (0);
+ if (nd->nd_flag & (ND_NFSV3 | ND_NFSV4)) {
+ if (nd->nd_procnum == NFSPROC_NOOP)
+ return (txdr_unsigned(nd->nd_repstat & 0xffff));
+ if (nd->nd_flag & ND_NFSV3)
+ errp = defaulterrp = nfsrv_v3errmap[nd->nd_procnum];
+ else if (nd->nd_repstat == EBADRPC)
+ return (txdr_unsigned(NFSERR_BADXDR));
+ else if (nd->nd_repstat == NFSERR_MINORVERMISMATCH ||
+ nd->nd_repstat == NFSERR_OPILLEGAL)
+ return (txdr_unsigned(nd->nd_repstat));
+ else
+ errp = defaulterrp = nfsrv_v4errmap[nd->nd_procnum];
+ while (*++errp)
+ if (*errp == nd->nd_repstat)
+ return (txdr_unsigned(nd->nd_repstat));
+ return (txdr_unsigned(*defaulterrp));
+ }
+ if (nd->nd_repstat <= ELAST)
+ return (txdr_unsigned(nfsrv_v2errmap[nd->nd_repstat - 1]));
+ return (txdr_unsigned(NFSERR_IO));
+}
+
+/*
+ * Check to see if setting a uid/gid is permitted when creating a new
+ * file object. (Called when uid and/or gid is specified in the
+ * settable attributes for V4.
+ */
+APPLESTATIC int
+nfsrv_checkuidgid(struct nfsrv_descript *nd, struct nfsvattr *nvap)
+{
+
+ /*
+ * If not setting either uid nor gid, it's OK.
+ */
+ if (NFSVNO_NOTSETUID(nvap) && NFSVNO_NOTSETGID(nvap))
+ return (0);
+ if ((NFSVNO_ISSETUID(nvap) && nvap->na_uid == nfsrv_defaultuid)
+ || (NFSVNO_ISSETGID(nvap) && nvap->na_gid == nfsrv_defaultgid))
+ return (NFSERR_BADOWNER);
+ if (nd->nd_cred->cr_uid == 0)
+ return (0);
+ if ((NFSVNO_ISSETUID(nvap) && nvap->na_uid != nd->nd_cred->cr_uid) ||
+ (NFSVNO_ISSETGID(nvap) && nvap->na_gid != nd->nd_cred->cr_gid &&
+ !groupmember(nvap->na_gid, nd->nd_cred)))
+ return (NFSERR_PERM);
+ return (0);
+}
+
+/*
+ * and this routine fixes up the settable attributes for V4 if allowed
+ * by nfsrv_checkuidgid().
+ */
+APPLESTATIC void
+nfsrv_fixattr(struct nfsrv_descript *nd, vnode_t vp,
+ struct nfsvattr *nvap, NFSACL_T *aclp, NFSPROC_T *p, nfsattrbit_t *attrbitp,
+ struct nfsexstuff *exp)
+{
+ int change = 0;
+ struct nfsvattr nva;
+ uid_t tuid;
+ int error;
+ nfsattrbit_t nattrbits;
+
+ /*
+ * Maybe this should be done for V2 and 3 but it never has been
+ * and nobody seems to be upset, so I think it's best not to change
+ * the V2 and 3 semantics.
+ */
+ if ((nd->nd_flag & ND_NFSV4) == 0)
+ return;
+ NFSVNO_ATTRINIT(&nva);
+ NFSZERO_ATTRBIT(&nattrbits);
+ tuid = nd->nd_cred->cr_uid;
+ if (NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_OWNER) &&
+ NFSVNO_ISSETUID(nvap) &&
+ nvap->na_uid != nd->nd_cred->cr_uid) {
+ if (nd->nd_cred->cr_uid == 0) {
+ nva.na_uid = nvap->na_uid;
+ change++;
+ NFSSETBIT_ATTRBIT(&nattrbits, NFSATTRBIT_OWNER);
+ } else {
+ NFSCLRBIT_ATTRBIT(attrbitp, NFSATTRBIT_OWNER);
+ }
+ }
+ if (NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_TIMEACCESSSET) &&
+ NFSVNO_ISSETATIME(nvap)) {
+ nva.na_atime = nvap->na_atime;
+ change++;
+ NFSSETBIT_ATTRBIT(&nattrbits, NFSATTRBIT_TIMEACCESSSET);
+ }
+ if (NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_TIMEMODIFYSET) &&
+ NFSVNO_ISSETMTIME(nvap)) {
+ nva.na_mtime = nvap->na_mtime;
+ change++;
+ NFSSETBIT_ATTRBIT(&nattrbits, NFSATTRBIT_TIMEMODIFYSET);
+ }
+ if (NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_OWNERGROUP) &&
+ NFSVNO_ISSETGID(nvap)) {
+ if (nvap->na_gid == nd->nd_cred->cr_gid ||
+ groupmember(nvap->na_gid, nd->nd_cred)) {
+ nd->nd_cred->cr_uid = 0;
+ nva.na_gid = nvap->na_gid;
+ change++;
+ NFSSETBIT_ATTRBIT(&nattrbits, NFSATTRBIT_OWNERGROUP);
+ } else {
+ NFSCLRBIT_ATTRBIT(attrbitp, NFSATTRBIT_OWNERGROUP);
+ }
+ }
+ if (change) {
+ error = nfsvno_setattr(vp, &nva, nd->nd_cred, p, exp);
+ if (error) {
+ NFSCLRALL_ATTRBIT(attrbitp, &nattrbits);
+ }
+ }
+ if (NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_SIZE) &&
+ NFSVNO_ISSETSIZE(nvap) && nvap->na_size != (u_quad_t)0) {
+ NFSCLRBIT_ATTRBIT(attrbitp, NFSATTRBIT_SIZE);
+ }
+#ifdef NFS4_ACL_EXTATTR_NAME
+ if (NFSISSET_ATTRBIT(attrbitp, NFSATTRBIT_ACL) &&
+ nfsrv_useacl != 0 && aclp != NULL) {
+ if (aclp->acl_cnt > 0) {
+ error = nfsrv_setacl(vp, aclp, nd->nd_cred, p);
+ if (error) {
+ NFSCLRBIT_ATTRBIT(attrbitp, NFSATTRBIT_ACL);
+ }
+ }
+ } else
+#endif
+ NFSCLRBIT_ATTRBIT(attrbitp, NFSATTRBIT_ACL);
+ nd->nd_cred->cr_uid = tuid;
+}
+
+/*
+ * Translate an ASCII hex digit to it's binary value. Return -1 if the
+ * char isn't a hex digit.
+ */
+static char
+nfsrv_hexdigit(char c, int *err)
+{
+
+ *err = 0;
+ if (c >= '0' && c <= '9')
+ return (c - '0');
+ if (c >= 'a' && c <= 'f')
+ return (c - 'a' + ((char)10));
+ if (c >= 'A' && c <= 'F')
+ return (c - 'A' + ((char)10));
+ /* Not valid ! */
+ *err = 1;
+ return (1); /* BOGUS */
+}
+
+/*
+ * Check to see if NFSERR_MOVED can be returned for this op. Return 1 iff
+ * it can be.
+ */
+APPLESTATIC int
+nfsrv_errmoved(int op)
+{
+ short *errp;
+
+ errp = nfsrv_v4errmap[op];
+ while (*errp != 0) {
+ if (*errp == NFSERR_MOVED)
+ return (1);
+ errp++;
+ }
+ return (0);
+}
+
+/*
+ * Fill in attributes for a Referral.
+ * (Return the number of bytes of XDR created.)
+ */
+APPLESTATIC int
+nfsrv_putreferralattr(struct nfsrv_descript *nd, nfsattrbit_t *retbitp,
+ struct nfsreferral *refp, int getattr, int *reterrp)
+{
+ u_int32_t *tl, *retnump;
+ u_char *cp, *cp2;
+ int prefixnum, retnum = 0, i, len, bitpos, rderrbit = 0, nonrefbit = 0;
+ int fslocationsbit = 0;
+ nfsattrbit_t tmpbits, refbits;
+
+ NFSREFERRAL_ATTRBIT(&refbits);
+ if (getattr)
+ NFSCLRBIT_ATTRBIT(&refbits, NFSATTRBIT_RDATTRERROR);
+ else if (NFSISSET_ATTRBIT(retbitp, NFSATTRBIT_RDATTRERROR))
+ rderrbit = 1;
+ if (NFSISSET_ATTRBIT(retbitp, NFSATTRBIT_FSLOCATIONS))
+ fslocationsbit = 1;
+
+ /*
+ * Check for the case where unsupported referral attributes are
+ * requested.
+ */
+ NFSSET_ATTRBIT(&tmpbits, retbitp);
+ NFSCLRALL_ATTRBIT(&tmpbits, &refbits);
+ if (NFSNONZERO_ATTRBIT(&tmpbits))
+ nonrefbit = 1;
+
+ if (nonrefbit && !fslocationsbit && (getattr || !rderrbit)) {
+ *reterrp = NFSERR_MOVED;
+ return (0);
+ }
+
+ /*
+ * Now we can fill in the attributes.
+ */
+ NFSSET_ATTRBIT(&tmpbits, retbitp);
+ NFSCLRNOT_ATTRBIT(&tmpbits, &refbits);
+
+ /*
+ * Put out the attribute bitmap for the ones being filled in
+ * and get the field for the number of attributes returned.
+ */
+ prefixnum = nfsrv_putattrbit(nd, &tmpbits);
+ NFSM_BUILD(retnump, u_int32_t *, NFSX_UNSIGNED);
+ prefixnum += NFSX_UNSIGNED;
+
+ /*
+ * Now, loop around filling in the attributes for each bit set.
+ */
+ for (bitpos = 0; bitpos < NFSATTRBIT_MAX; bitpos++) {
+ if (NFSISSET_ATTRBIT(&tmpbits, bitpos)) {
+ switch (bitpos) {
+ case NFSATTRBIT_TYPE:
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl = txdr_unsigned(NFDIR);
+ retnum += NFSX_UNSIGNED;
+ break;
+ case NFSATTRBIT_FSID:
+ NFSM_BUILD(tl, u_int32_t *, NFSX_V4FSID);
+ *tl++ = 0;
+ *tl++ = txdr_unsigned(NFSV4ROOT_FSID0);
+ *tl++ = 0;
+ *tl = txdr_unsigned(NFSV4ROOT_REFERRAL);
+ retnum += NFSX_V4FSID;
+ break;
+ case NFSATTRBIT_RDATTRERROR:
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ if (nonrefbit)
+ *tl = txdr_unsigned(NFSERR_MOVED);
+ else
+ *tl = 0;
+ retnum += NFSX_UNSIGNED;
+ break;
+ case NFSATTRBIT_FSLOCATIONS:
+ retnum += nfsm_strtom(nd, "/", 1);
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl = txdr_unsigned(refp->nfr_srvcnt);
+ retnum += NFSX_UNSIGNED;
+ cp = refp->nfr_srvlist;
+ for (i = 0; i < refp->nfr_srvcnt; i++) {
+ NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
+ *tl = txdr_unsigned(1);
+ retnum += NFSX_UNSIGNED;
+ cp2 = STRCHR(cp, ':');
+ if (cp2 != NULL)
+ len = cp2 - cp;
+ else
+ len = 1;
+ retnum += nfsm_strtom(nd, cp, len);
+ if (cp2 != NULL)
+ cp = cp2 + 1;
+ cp2 = STRCHR(cp, ',');
+ if (cp2 != NULL)
+ len = cp2 - cp;
+ else
+ len = strlen(cp);
+ retnum += nfsm_strtom(nd, cp, len);
+ if (cp2 != NULL)
+ cp = cp2 + 1;
+ }
+ break;
+ case NFSATTRBIT_MOUNTEDONFILEID:
+ NFSM_BUILD(tl, u_int32_t *, NFSX_HYPER);
+ *tl++ = 0;
+ *tl = txdr_unsigned(refp->nfr_dfileno);
+ retnum += NFSX_HYPER;
+ break;
+ default:
+ printf("EEK! Bad V4 refattr bitpos=%d\n", bitpos);
+ };
+ }
+ }
+ *retnump = txdr_unsigned(retnum);
+ return (retnum + prefixnum);
+}
+
+/*
+ * Parse a file name out of a request.
+ */
+APPLESTATIC int
+nfsrv_parsename(struct nfsrv_descript *nd, char *bufp, u_long *hashp,
+ NFSPATHLEN_T *outlenp)
+{
+ char *fromcp, *tocp, val = '\0';
+ mbuf_t md;
+ int i;
+ int rem, len, error = 0, pubtype = 0, outlen = 0, percent = 0;
+ char digit;
+ u_int32_t *tl;
+ u_long hash = 0;
+
+ if (hashp != NULL)
+ *hashp = 0;
+ tocp = bufp;
+ /*
+ * For V4, check for lookup parent.
+ * Otherwise, get the component name.
+ */
+ if ((nd->nd_flag & ND_NFSV4) && nd->nd_procnum == NFSV4OP_LOOKUPP) {
+ *tocp++ = '.';
+ hash += ((u_char)'.');
+ *tocp++ = '.';
+ hash += ((u_char)'.');
+ outlen = 2;
+ } else {
+ /*
+ * First, get the name length.
+ */
+ NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
+ len = fxdr_unsigned(int, *tl);
+ if (len > NFS_MAXNAMLEN) {
+ nd->nd_repstat = NFSERR_NAMETOL;
+ return (0);
+ } else if (len <= 0) {
+ nd->nd_repstat = NFSERR_INVAL;
+ return (0);
+ }
+
+ /*
+ * Now, copy the component name into the buffer.
+ */
+ fromcp = nd->nd_dpos;
+ md = nd->nd_md;
+ rem = NFSMTOD(md, caddr_t) + mbuf_len(md) - fromcp;
+ for (i = 0; i < len; i++) {
+ while (rem == 0) {
+ md = mbuf_next(md);
+ if (md == NULL)
+ return (EBADRPC);
+ fromcp = NFSMTOD(md, caddr_t);
+ rem = mbuf_len(md);
+ }
+ if (*fromcp == '\0') {
+ nd->nd_repstat = EACCES;
+ return (0);
+ }
+ /*
+ * For lookups on the public filehandle, do some special
+ * processing on the name. (The public file handle is the
+ * root of the public file system for this server.)
+ */
+ if (nd->nd_flag & ND_PUBLOOKUP) {
+ /*
+ * If the first char is ASCII, it is a canonical
+ * path, otherwise it is a native path. (RFC2054
+ * doesn't actually state what it is if the first
+ * char isn't ASCII or 0x80, so I assume native.)
+ * pubtype == 1 -> native path
+ * pubtype == 2 -> canonical path
+ */
+ if (i == 0) {
+ if (*fromcp & 0x80) {
+ /*
+ * Since RFC2054 doesn't indicate
+ * that a native path of just 0x80
+ * isn't allowed, I'll replace the
+ * 0x80 with '/' instead of just
+ * throwing it away.
+ */
+ *fromcp = '/';
+ pubtype = 1;
+ } else {
+ pubtype = 2;
+ }
+ }
+ /*
+ * '/' only allowed in a native path
+ */
+ if (*fromcp == '/' && pubtype != 1) {
+ nd->nd_repstat = EACCES;
+ return (0);
+ }
+
+ /*
+ * For the special case of 2 hex digits after a
+ * '%' in an absolute path, calculate the value.
+ * percent == 1 -> indicates "get first hex digit"
+ * percent == 2 -> indicates "get second hex digit"
+ */
+ if (percent > 0) {
+ digit = nfsrv_hexdigit(*fromcp, &error);
+ if (error) {
+ nd->nd_repstat = EACCES;
+ return (0);
+ }
+ if (percent == 1) {
+ val = (digit << 4);
+ percent = 2;
+ } else {
+ val += digit;
+ percent = 0;
+ *tocp++ = val;
+ hash += ((u_char)val);
+ outlen++;
+ }
+ } else {
+ if (*fromcp == '%' && pubtype == 2) {
+ /*
+ * Must be followed by 2 hex digits
+ */
+ if ((len - i) < 3) {
+ nd->nd_repstat = EACCES;
+ return (0);
+ }
+ percent = 1;
+ } else {
+ *tocp++ = *fromcp;
+ hash += ((u_char)*fromcp);
+ outlen++;
+ }
+ }
+ } else {
+ /*
+ * Normal, non lookup on public, name.
+ */
+ if (*fromcp == '/') {
+ if (nd->nd_flag & ND_NFSV4)
+ nd->nd_repstat = NFSERR_BADNAME;
+ else
+ nd->nd_repstat = EACCES;
+ return (0);
+ }
+ hash += ((u_char)*fromcp);
+ *tocp++ = *fromcp;
+ outlen++;
+ }
+ fromcp++;
+ rem--;
+ }
+ nd->nd_md = md;
+ nd->nd_dpos = fromcp;
+ i = NFSM_RNDUP(len) - len;
+ if (i > 0) {
+ if (rem >= i) {
+ nd->nd_dpos += i;
+ } else {
+ error = nfsm_advance(nd, i, rem);
+ if (error)
+ return (error);
+ }
+ }
+
+ /*
+ * For v4, don't allow lookups of '.' or '..' and
+ * also check for non-utf8 strings.
+ */
+ if (nd->nd_flag & ND_NFSV4) {
+ if ((outlen == 1 && bufp[0] == '.') ||
+ (outlen == 2 && bufp[0] == '.' &&
+ bufp[1] == '.')) {
+ nd->nd_repstat = NFSERR_BADNAME;
+ return (0);
+ }
+ if (nfsrv_checkutf8((u_int8_t *)bufp, outlen)) {
+ nd->nd_repstat = NFSERR_INVAL;
+ return (0);
+ }
+ }
+ }
+ *tocp = '\0';
+ *outlenp = (size_t)outlen;
+ if (hashp != NULL)
+ *hashp = hash;
+nfsmout:
+ return (error);
+}
+
+/*
+ * Check the tcp socket sequence number has been acknowledged.
+ */
+int
+nfsrv_checksockseqnum(struct socket *so, tcp_seq tcpseqval)
+{
+ tcp_seq maxseq, unaseq;
+ int error, ret;
+
+ error = nfsrv_getsocksndseq(so, &maxseq, &unaseq);
+ if (error)
+ return (0);
+ ret = SEQ_GEQ(unaseq, tcpseqval);
+ return (ret);
+}
+
+/*
+ * Get the tcp sequence number to be acknowledged.
+ */
+int
+nfsrv_getsockseqnum(struct socket *so, tcp_seq *tcpseqp)
+{
+ tcp_seq maxseq, unaseq;
+ u_int sbcc;
+ int error;
+
+ sbcc = so->so_snd.sb_cc;
+ error = nfsrv_getsocksndseq(so, &maxseq, &unaseq);
+ if (error)
+ return (0);
+ /*
+ * Set the seq# to a value that will
+ * be at least the end of the reply.
+ * When this sequence# is acknowledged
+ * by the client, the client has received
+ * the reply.
+ */
+ *tcpseqp = sbcc + maxseq;
+ return (1);
+}
+
+void
+nfsd_init(void)
+{
+ int i;
+ static int inited = 0;
+
+ if (inited)
+ return;
+ inited = 1;
+
+ /*
+ * Initialize client queues. Don't free/reinitialize
+ * them when nfsds are restarted.
+ */
+ for (i = 0; i < NFSCLIENTHASHSIZE; i++)
+ LIST_INIT(&nfsclienthash[i]);
+ for (i = 0; i < NFSLOCKHASHSIZE; i++)
+ LIST_INIT(&nfslockhash[i]);
+
+ /* and the v2 pubfh should be all zeros */
+ NFSBZERO(nfs_v2pubfh, NFSX_V2FH);
+}
+
OpenPOWER on IntegriCloud