#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <netinet/in.h>
#include "rpcv2.h"
#include "nfsproto.h"
#include "nfs.h"
#include "nfsrvcache.h"
#ifndef NFS_NOSERVER
static long numnfsrvcache;
static long desirednfsrvcache;
#define NFSRCHASH(xid) \
(&nfsrvhashtbl[((xid) + ((xid) >> 24)) & nfsrvhash])
static LIST_HEAD(nfsrvhash, nfsrvcache) *nfsrvhashtbl;
static TAILQ_HEAD(nfsrvlru, nfsrvcache) nfsrvlruhead;
static u_long nfsrvhash;
#define TRUE 1
#define FALSE 0
struct lwkt_token srvcache_token = LWKT_TOKEN_INITIALIZER(srvcache_token);
static int nonidempotent[NFS_NPROCS] = {
FALSE,
FALSE,
TRUE,
FALSE,
FALSE,
FALSE,
FALSE,
TRUE,
TRUE,
TRUE,
TRUE,
TRUE,
TRUE,
TRUE,
TRUE,
TRUE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
};
static int nfsv2_repstat[NFS_NPROCS] = {
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
FALSE,
TRUE,
TRUE,
TRUE,
TRUE,
FALSE,
TRUE,
FALSE,
FALSE,
};
static void
nfsrvcache_size_change(void)
{
desirednfsrvcache = nmbclusters / 2;
desirednfsrvcache = MIN(desirednfsrvcache, NFSRVCACHE_MAX_SIZE);
desirednfsrvcache = MAX(desirednfsrvcache, NFSRVCACHE_MIN_SIZE);
}
void
nfsrv_initcache(void)
{
nfsrvcache_size_change();
nfsrvhashtbl = hashinit(desirednfsrvcache, M_NFSD, &nfsrvhash);
TAILQ_INIT(&nfsrvlruhead);
}
void
nfsrv_destroycache(void)
{
KKASSERT(TAILQ_EMPTY(&nfsrvlruhead));
hashdestroy(nfsrvhashtbl, M_NFSD, nfsrvhash);
}
int
nfsrv_getcache(struct nfsrv_descript *nd, struct nfssvc_sock *slp,
struct mbuf **repp)
{
struct nfsrvcache *rp;
struct mbuf *mb;
struct sockaddr_in *saddr;
caddr_t bpos;
int ret;
if (!nd->nd_nam2)
return (RC_DOIT);
lwkt_gettoken(&srvcache_token);
loop:
for (rp = NFSRCHASH(nd->nd_retxid)->lh_first; rp != NULL;
rp = rp->rc_hash.le_next) {
if (nd->nd_retxid == rp->rc_xid && nd->nd_procnum == rp->rc_proc &&
netaddr_match(AF_INET, &rp->rc_haddr, nd->nd_nam)) {
NFS_DPF(RC, ("H%03x", rp->rc_xid & 0xfff));
if ((rp->rc_flag & RC_LOCKED) != 0) {
rp->rc_flag |= RC_WANTED;
tsleep((caddr_t)rp, 0, "nfsrc", 0);
goto loop;
}
rp->rc_flag |= RC_LOCKED;
if (TAILQ_NEXT(rp, rc_lru) != NULL) {
TAILQ_REMOVE(&nfsrvlruhead, rp, rc_lru);
TAILQ_INSERT_TAIL(&nfsrvlruhead, rp, rc_lru);
}
if (rp->rc_state == RC_UNUSED)
panic("nfsrv cache");
if (rp->rc_state == RC_INPROG) {
nfsstats.srvcache_inproghits++;
ret = RC_DROPIT;
} else if (rp->rc_flag & RC_REPSTATUS) {
nfsstats.srvcache_nonidemdonehits++;
nfs_rephead(0, nd, slp, rp->rc_status,
repp, &mb, &bpos);
ret = RC_REPLY;
} else if (rp->rc_flag & RC_REPMBUF) {
nfsstats.srvcache_nonidemdonehits++;
*repp = m_copym(rp->rc_reply, 0, M_COPYALL,
M_WAITOK);
ret = RC_REPLY;
} else {
nfsstats.srvcache_idemdonehits++;
rp->rc_state = RC_INPROG;
ret = RC_DOIT;
}
rp->rc_flag &= ~RC_LOCKED;
if (rp->rc_flag & RC_WANTED) {
rp->rc_flag &= ~RC_WANTED;
wakeup((caddr_t)rp);
}
lwkt_reltoken(&srvcache_token);
return (ret);
}
}
nfsstats.srvcache_misses++;
NFS_DPF(RC, ("M%03x", nd->nd_retxid & 0xfff));
if (numnfsrvcache < desirednfsrvcache) {
rp = kmalloc((u_long)sizeof *rp, M_NFSD, M_WAITOK | M_ZERO);
numnfsrvcache++;
rp->rc_flag = RC_LOCKED;
} else {
rp = TAILQ_FIRST(&nfsrvlruhead);
while ((rp->rc_flag & RC_LOCKED) != 0) {
rp->rc_flag |= RC_WANTED;
tsleep((caddr_t)rp, 0, "nfsrc", 0);
rp = TAILQ_FIRST(&nfsrvlruhead);
}
rp->rc_flag |= RC_LOCKED;
LIST_REMOVE(rp, rc_hash);
TAILQ_REMOVE(&nfsrvlruhead, rp, rc_lru);
if (rp->rc_flag & RC_REPMBUF) {
m_freem(rp->rc_reply);
rp->rc_reply = NULL;
rp->rc_flag &= ~RC_REPMBUF;
}
if (rp->rc_flag & RC_NAM) {
kfree(rp->rc_nam, M_SONAME);
rp->rc_nam = NULL;
rp->rc_flag &= ~RC_NAM;
}
}
TAILQ_INSERT_TAIL(&nfsrvlruhead, rp, rc_lru);
rp->rc_state = RC_INPROG;
rp->rc_xid = nd->nd_retxid;
saddr = (struct sockaddr_in *)nd->nd_nam;
switch (saddr->sin_family) {
case AF_INET:
rp->rc_flag |= RC_INETADDR;
rp->rc_inetaddr = saddr->sin_addr.s_addr;
break;
default:
rp->rc_flag |= RC_NAM;
rp->rc_nam = dup_sockaddr(nd->nd_nam);
break;
}
rp->rc_proc = nd->nd_procnum;
LIST_INSERT_HEAD(NFSRCHASH(nd->nd_retxid), rp, rc_hash);
rp->rc_flag &= ~RC_LOCKED;
if (rp->rc_flag & RC_WANTED) {
rp->rc_flag &= ~RC_WANTED;
wakeup((caddr_t)rp);
}
lwkt_reltoken(&srvcache_token);
return (RC_DOIT);
}
void
nfsrv_updatecache(struct nfsrv_descript *nd, int repvalid, struct mbuf *repmbuf)
{
struct nfsrvcache *rp;
if (!nd->nd_nam2)
return;
lwkt_gettoken(&srvcache_token);
loop:
for (rp = NFSRCHASH(nd->nd_retxid)->lh_first; rp != NULL;
rp = rp->rc_hash.le_next) {
if (nd->nd_retxid == rp->rc_xid && nd->nd_procnum == rp->rc_proc &&
netaddr_match(AF_INET, &rp->rc_haddr, nd->nd_nam)) {
NFS_DPF(RC, ("U%03x", rp->rc_xid & 0xfff));
if ((rp->rc_flag & RC_LOCKED) != 0) {
rp->rc_flag |= RC_WANTED;
tsleep((caddr_t)rp, 0, "nfsrc", 0);
goto loop;
}
rp->rc_flag |= RC_LOCKED;
if (rp->rc_state == RC_DONE) {
if (rp->rc_flag & RC_REPMBUF) {
m_freem(rp->rc_reply);
rp->rc_reply = NULL;
rp->rc_flag &= ~RC_REPMBUF;
}
}
rp->rc_state = RC_DONE;
if (repvalid && nonidempotent[nd->nd_procnum]) {
if ((nd->nd_flag & ND_NFSV3) == 0 &&
nfsv2_repstat[nfsv2_procid[nd->nd_procnum]]) {
rp->rc_status = nd->nd_repstat;
rp->rc_flag |= RC_REPSTATUS;
} else {
if (rp->rc_flag & RC_REPMBUF) {
m_freem(rp->rc_reply);
rp->rc_reply = NULL;
rp->rc_flag &= ~RC_REPMBUF;
}
rp->rc_reply = m_copym(repmbuf, 0,
M_COPYALL, M_WAITOK);
rp->rc_flag |= RC_REPMBUF;
}
}
rp->rc_flag &= ~RC_LOCKED;
if (rp->rc_flag & RC_WANTED) {
rp->rc_flag &= ~RC_WANTED;
wakeup((caddr_t)rp);
}
break;
}
}
lwkt_reltoken(&srvcache_token);
NFS_DPF(RC, ("L%03x", nd->nd_retxid & 0xfff));
}
void
nfsrv_cleancache(void)
{
struct nfsrvcache *rp;
lwkt_gettoken(&srvcache_token);
while ((rp = TAILQ_FIRST(&nfsrvlruhead)) != NULL) {
if (rp->rc_flag & RC_LOCKED) {
rp->rc_flag |= RC_WANTED;
tsleep((caddr_t)rp, 0, "nfsrc", 0);
continue;
}
LIST_REMOVE(rp, rc_hash);
TAILQ_REMOVE(&nfsrvlruhead, rp, rc_lru);
if (rp->rc_flag & RC_REPMBUF) {
m_freem(rp->rc_reply);
rp->rc_reply = NULL;
rp->rc_flag &= ~RC_REPMBUF;
}
if (rp->rc_flag & RC_NAM) {
kfree(rp->rc_nam, M_SONAME);
rp->rc_nam = NULL;
rp->rc_flag &= ~RC_NAM;
}
kfree(rp, M_NFSD);
}
numnfsrvcache = 0;
lwkt_reltoken(&srvcache_token);
}
#endif