xref: /openbsd/sys/nfs/nfs_srvcache.c (revision 17a0233e)
1 /*	$OpenBSD: nfs_srvcache.c,v 1.20 2008/10/20 13:40:19 blambert Exp $	*/
2 /*	$NetBSD: nfs_srvcache.c,v 1.12 1996/02/18 11:53:49 fvdl Exp $	*/
3 
4 /*
5  * Copyright (c) 1989, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Rick Macklem at The University of Guelph.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  *	@(#)nfs_srvcache.c	8.3 (Berkeley) 3/30/95
36  */
37 
38 /*
39  * Reference: Chet Juszczak, "Improving the Performance and Correctness
40  *		of an NFS Server", in Proc. Winter 1989 USENIX Conference,
41  *		pages 53-63. San Diego, February 1989.
42  */
43 #include <sys/param.h>
44 #include <sys/mount.h>
45 #include <sys/kernel.h>
46 #include <sys/systm.h>
47 #include <sys/proc.h>
48 #include <sys/mbuf.h>
49 #include <sys/malloc.h>
50 #include <sys/socket.h>
51 #include <sys/queue.h>
52 
53 #include <netinet/in.h>
54 #include <nfs/rpcv2.h>
55 #include <nfs/nfsproto.h>
56 #include <nfs/nfs.h>
57 #include <nfs/nfsrvcache.h>
58 #include <nfs/nfs_var.h>
59 
60 extern struct nfsstats nfsstats;
61 extern int nfsv2_procid[NFS_NPROCS];
62 long numnfsrvcache, desirednfsrvcache = NFSRVCACHESIZ;
63 
64 struct nfsrvcache	*nfsrv_lookupcache(struct nfsrv_descript *);
65 
66 #define	NFSRCHASH(xid)	\
67 	(&nfsrvhashtbl[((xid) + ((xid) >> 24)) & nfsrvhash])
68 LIST_HEAD(nfsrvhash, nfsrvcache) *nfsrvhashtbl;
69 TAILQ_HEAD(nfsrvlru, nfsrvcache) nfsrvlruhead;
70 u_long nfsrvhash;
71 
72 #define	TRUE	1
73 #define	FALSE	0
74 
75 #define	NETFAMILY(rp)	\
76 	(((rp)->rc_flag & RC_INETADDR) ? AF_INET : AF_UNSPEC)
77 
78 /* Array that defines which nfs rpc's are nonidempotent */
79 int nonidempotent[NFS_NPROCS] = {
80 	FALSE,
81 	FALSE,
82 	TRUE,
83 	FALSE,
84 	FALSE,
85 	FALSE,
86 	FALSE,
87 	TRUE,
88 	TRUE,
89 	TRUE,
90 	TRUE,
91 	TRUE,
92 	TRUE,
93 	TRUE,
94 	TRUE,
95 	TRUE,
96 	FALSE,
97 	FALSE,
98 	FALSE,
99 	FALSE,
100 	FALSE,
101 	FALSE,
102 	FALSE,
103 	FALSE,
104 	FALSE,
105 	FALSE,
106 };
107 
108 /* True iff the rpc reply is an nfs status ONLY! */
109 int nfsv2_repstat[NFS_NPROCS] = {
110 	FALSE,
111 	FALSE,
112 	FALSE,
113 	FALSE,
114 	FALSE,
115 	FALSE,
116 	FALSE,
117 	FALSE,
118 	FALSE,
119 	FALSE,
120 	TRUE,
121 	TRUE,
122 	TRUE,
123 	TRUE,
124 	FALSE,
125 	TRUE,
126 	FALSE,
127 	FALSE,
128 };
129 
130 /* Initialize the server request cache list */
131 void
132 nfsrv_initcache(void)
133 {
134 
135 	nfsrvhashtbl = hashinit(desirednfsrvcache, M_NFSD, M_WAITOK, &nfsrvhash);
136 	TAILQ_INIT(&nfsrvlruhead);
137 }
138 
139 /*
140  * Look for the request in the cache
141  * If found then
142  *    return action and optionally reply
143  * else
144  *    insert it in the cache
145  *
146  * The rules are as follows:
147  * - if in progress, return DROP request
148  * - if completed within DELAY of the current time, return DROP it
149  * - if completed a longer time ago return REPLY if the reply was cached or
150  *   return DOIT
151  * Update/add new request at end of lru list
152  */
153 int
154 nfsrv_getcache(struct nfsrv_descript *nd, struct nfssvc_sock *slp,
155     struct mbuf **repp)
156 {
157 	struct nfsrvcache *rp;
158 	struct mbuf *mb;
159 	struct sockaddr_in *saddr;
160 	int ret;
161 
162 	/*
163 	 * Don't cache recent requests for reliable transport protocols.
164 	 * (Maybe we should for the case of a reconnect, but..)
165 	 */
166 	if (!nd->nd_nam2)
167 		return (RC_DOIT);
168 
169 	rp = nfsrv_lookupcache(nd);
170 	if (rp) {
171 		/* If not at end of LRU chain, move it there */
172 		if (TAILQ_NEXT(rp, rc_lru)) {
173 			TAILQ_REMOVE(&nfsrvlruhead, rp, rc_lru);
174 			TAILQ_INSERT_TAIL(&nfsrvlruhead, rp, rc_lru);
175 		}
176 		if (rp->rc_state == RC_UNUSED)
177 			panic("nfsrv cache");
178 		if (rp->rc_state == RC_INPROG) {
179 			nfsstats.srvcache_inproghits++;
180 			ret = RC_DROPIT;
181 		} else if (rp->rc_flag & RC_REPSTATUS) {
182 			nfsstats.srvcache_nonidemdonehits++;
183 			nfs_rephead(0, nd, slp, rp->rc_status, repp, &mb);
184 			ret = RC_REPLY;
185 		} else if (rp->rc_flag & RC_REPMBUF) {
186 			nfsstats.srvcache_nonidemdonehits++;
187 			*repp = m_copym(rp->rc_reply, 0, M_COPYALL, M_WAIT);
188 			ret = RC_REPLY;
189 		} else {
190 			nfsstats.srvcache_idemdonehits++;
191 			rp->rc_state = RC_INPROG;
192 			ret = RC_DOIT;
193 		}
194 		rp->rc_flag &= ~RC_LOCKED;
195 		if (rp->rc_flag & RC_WANTED) {
196 			rp->rc_flag &= ~RC_WANTED;
197 			wakeup(rp);
198 		}
199 		return (ret);
200 	}
201 
202 	nfsstats.srvcache_misses++;
203 	if (numnfsrvcache < desirednfsrvcache) {
204 		rp = malloc(sizeof(*rp), M_NFSD, M_WAITOK|M_ZERO);
205 		numnfsrvcache++;
206 		rp->rc_flag = RC_LOCKED;
207 	} else {
208 		rp = TAILQ_FIRST(&nfsrvlruhead);
209 		while ((rp->rc_flag & RC_LOCKED) != 0) {
210 			rp->rc_flag |= RC_WANTED;
211 			tsleep(rp, PZERO-1, "nfsrc", 0);
212 			rp = TAILQ_FIRST(&nfsrvlruhead);
213 		}
214 		rp->rc_flag |= RC_LOCKED;
215 		LIST_REMOVE(rp, rc_hash);
216 		TAILQ_REMOVE(&nfsrvlruhead, rp, rc_lru);
217 		if (rp->rc_flag & RC_REPMBUF)
218 			m_freem(rp->rc_reply);
219 		if (rp->rc_flag & RC_NAM)
220 			m_freem(rp->rc_nam);
221 		rp->rc_flag &= (RC_LOCKED | RC_WANTED);
222 	}
223 	TAILQ_INSERT_TAIL(&nfsrvlruhead, rp, rc_lru);
224 	rp->rc_state = RC_INPROG;
225 	rp->rc_xid = nd->nd_retxid;
226 	saddr = mtod(nd->nd_nam, struct sockaddr_in *);
227 	switch (saddr->sin_family) {
228 	case AF_INET:
229 		rp->rc_flag |= RC_INETADDR;
230 		rp->rc_inetaddr = saddr->sin_addr.s_addr;
231 		break;
232 	default:
233 		rp->rc_flag |= RC_NAM;
234 		rp->rc_nam = m_copym(nd->nd_nam, 0, M_COPYALL, M_WAIT);
235 		break;
236 	};
237 	rp->rc_proc = nd->nd_procnum;
238 	LIST_INSERT_HEAD(NFSRCHASH(nd->nd_retxid), rp, rc_hash);
239 	rp->rc_flag &= ~RC_LOCKED;
240 	if (rp->rc_flag & RC_WANTED) {
241 		rp->rc_flag &= ~RC_WANTED;
242 		wakeup(rp);
243 	}
244 	return (RC_DOIT);
245 }
246 
247 /* Update a request cache entry after the rpc has been done */
248 void
249 nfsrv_updatecache(struct nfsrv_descript *nd, int repvalid,
250     struct mbuf *repmbuf)
251 {
252 	struct nfsrvcache *rp;
253 
254 	if (!nd->nd_nam2)
255 		return;
256 
257 	rp = nfsrv_lookupcache(nd);
258 	if (rp) {
259 		rp->rc_state = RC_DONE;
260 		/*
261 		 * If we have a valid reply update status and save
262 		 * the reply for non-idempotent rpc's.
263 		 */
264 		if (repvalid && nonidempotent[nd->nd_procnum]) {
265 			if ((nd->nd_flag & ND_NFSV3) == 0 &&
266 			  nfsv2_repstat[nfsv2_procid[nd->nd_procnum]]) {
267 				rp->rc_status = nd->nd_repstat;
268 				rp->rc_flag |= RC_REPSTATUS;
269 			} else {
270 				rp->rc_reply = m_copym(repmbuf, 0, M_COPYALL,
271 				    M_WAIT);
272 				rp->rc_flag |= RC_REPMBUF;
273 			}
274 		}
275 		rp->rc_flag &= ~RC_LOCKED;
276 		if (rp->rc_flag & RC_WANTED) {
277 			rp->rc_flag &= ~RC_WANTED;
278 			wakeup(rp);
279 		}
280 		return;
281 	}
282 }
283 
284 /* Clean out the cache. Called when the last nfsd terminates. */
285 void
286 nfsrv_cleancache(void)
287 {
288 	struct nfsrvcache *rp, *nextrp;
289 
290 	for (rp = TAILQ_FIRST(&nfsrvlruhead); rp != NULL; rp = nextrp) {
291 		nextrp = TAILQ_NEXT(rp, rc_lru);
292 		LIST_REMOVE(rp, rc_hash);
293 		TAILQ_REMOVE(&nfsrvlruhead, rp, rc_lru);
294 		if (rp->rc_flag & RC_REPMBUF)
295 			m_freem(rp->rc_reply);
296 		if (rp->rc_flag & RC_NAM)
297 			m_freem(rp->rc_nam);
298 		free(rp, M_NFSD);
299 	}
300 	numnfsrvcache = 0;
301 }
302 
303 struct nfsrvcache *
304 nfsrv_lookupcache(struct nfsrv_descript *nd)
305 {
306 	struct nfsrvcache	*rp;
307 
308 loop:
309 	LIST_FOREACH(rp, NFSRCHASH(nd->nd_retxid), rc_hash) {
310 		if (nd->nd_retxid == rp->rc_xid &&
311 		    nd->nd_procnum == rp->rc_proc &&
312 		    netaddr_match(NETFAMILY(rp), &rp->rc_haddr, nd->nd_nam)) {
313 			if ((rp->rc_flag & RC_LOCKED)) {
314 				rp->rc_flag |= RC_WANTED;
315 				tsleep(rp, PZERO - 1, "nfsrc", 0);
316 				goto loop;
317 			}
318 			rp->rc_flag |= RC_LOCKED;
319 			return (rp);
320 		}
321 	}
322 
323 	return (NULL);
324 }
325