xref: /xnu-8019.80.24/bsd/nfs/nfs_srvcache.c (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
1 /*
2  * Copyright (c) 2000-2010 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 /* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */
29 /*
30  * Copyright (c) 1989, 1993
31  *	The Regents of the University of California.  All rights reserved.
32  *
33  * This code is derived from software contributed to Berkeley by
34  * Rick Macklem at The University of Guelph.
35  *
36  * Redistribution and use in source and binary forms, with or without
37  * modification, are permitted provided that the following conditions
38  * are met:
39  * 1. Redistributions of source code must retain the above copyright
40  *    notice, this list of conditions and the following disclaimer.
41  * 2. Redistributions in binary form must reproduce the above copyright
42  *    notice, this list of conditions and the following disclaimer in the
43  *    documentation and/or other materials provided with the distribution.
44  * 3. All advertising materials mentioning features or use of this software
45  *    must display the following acknowledgement:
46  *	This product includes software developed by the University of
47  *	California, Berkeley and its contributors.
48  * 4. Neither the name of the University nor the names of its contributors
49  *    may be used to endorse or promote products derived from this software
50  *    without specific prior written permission.
51  *
52  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
53  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
54  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
55  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
56  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
57  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
58  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
59  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
60  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
61  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
62  * SUCH DAMAGE.
63  *
64  *	@(#)nfs_srvcache.c	8.3 (Berkeley) 3/30/95
65  * FreeBSD-Id: nfs_srvcache.c,v 1.15 1997/10/12 20:25:46 phk Exp $
66  */
67 
68 #include <nfs/nfs_conf.h>
69 #if CONFIG_NFS_SERVER
70 
71 /*
72  * Reference: Chet Juszczak, "Improving the Performance and Correctness
73  *		of an NFS Server", in Proc. Winter 1989 USENIX Conference,
74  *		pages 53-63. San Diego, February 1989.
75  */
76 #include <sys/param.h>
77 #include <sys/vnode.h>
78 #include <sys/mount_internal.h>
79 #include <sys/kernel.h>
80 #include <sys/systm.h>
81 #include <sys/proc.h>
82 #include <sys/kpi_mbuf.h>
83 #include <sys/malloc.h>
84 #include <sys/socket.h>
85 #include <libkern/OSAtomic.h>
86 
87 #include <netinet/in.h>
88 #include <nfs/rpcv2.h>
89 #include <nfs/nfsproto.h>
90 #include <nfs/nfs.h>
91 #include <nfs/nfsrvcache.h>
92 
93 extern int nfsv2_procid[NFS_NPROCS];
94 static int nfsrv_reqcache_count;
95 int nfsrv_reqcache_size = NFSRVCACHESIZ;
96 
97 #define NFSRCHASH(xid) \
98 	(&nfsrv_reqcache_hashtbl[((xid) + ((xid) >> 24)) & nfsrv_reqcache_hash])
99 LIST_HEAD(nfsrv_reqcache_hash, nfsrvcache) * nfsrv_reqcache_hashtbl;
100 TAILQ_HEAD(nfsrv_reqcache_lru, nfsrvcache) nfsrv_reqcache_lruhead;
101 u_long nfsrv_reqcache_hash;
102 
103 static LCK_GRP_DECLARE(nfsrv_reqcache_lck_grp, "nfsrv_reqcache");
104 LCK_MTX_DECLARE(nfsrv_reqcache_mutex, &nfsrv_reqcache_lck_grp);
105 
106 /*
107  * Static array that defines which nfs rpc's are nonidempotent
108  */
109 static int nonidempotent[NFS_NPROCS] = {
110 	FALSE,
111 	FALSE,
112 	TRUE,
113 	FALSE,
114 	FALSE,
115 	FALSE,
116 	FALSE,
117 	TRUE,
118 	TRUE,
119 	TRUE,
120 	TRUE,
121 	TRUE,
122 	TRUE,
123 	TRUE,
124 	TRUE,
125 	TRUE,
126 	FALSE,
127 	FALSE,
128 	FALSE,
129 	FALSE,
130 	FALSE,
131 	FALSE,
132 	FALSE,
133 };
134 
135 /* True iff the rpc reply is an nfs status ONLY! */
136 static int nfsv2_repstat[NFS_NPROCS] = {
137 	FALSE,
138 	FALSE,
139 	FALSE,
140 	FALSE,
141 	FALSE,
142 	FALSE,
143 	FALSE,
144 	FALSE,
145 	FALSE,
146 	FALSE,
147 	TRUE,
148 	TRUE,
149 	TRUE,
150 	TRUE,
151 	FALSE,
152 	TRUE,
153 	FALSE,
154 	FALSE,
155 };
156 
157 /*
158  * Initialize the server request cache list
159  */
160 void
nfsrv_initcache(void)161 nfsrv_initcache(void)
162 {
163 	if (nfsrv_reqcache_size <= 0) {
164 		return;
165 	}
166 
167 	lck_mtx_lock(&nfsrv_reqcache_mutex);
168 	/* init nfs server request cache hash table */
169 	nfsrv_reqcache_hashtbl = hashinit(nfsrv_reqcache_size, M_NFSD, &nfsrv_reqcache_hash);
170 	TAILQ_INIT(&nfsrv_reqcache_lruhead);
171 	lck_mtx_unlock(&nfsrv_reqcache_mutex);
172 }
173 
174 /*
175  * This function compares two net addresses by family and returns TRUE
176  * if they are the same host.
177  * If there is any doubt, return FALSE.
178  * The AF_INET family is handled as a special case so that address mbufs
179  * don't need to be saved to store "struct in_addr", which is only 4 bytes.
180  * Ditto for AF_INET6 which is only 16 bytes.
181  */
182 static int
netaddr_match(int family,union nethostaddr * haddr,mbuf_t nam)183 netaddr_match(
184 	int family,
185 	union nethostaddr *haddr,
186 	mbuf_t nam)
187 {
188 	struct sockaddr_in *inetaddr;
189 	struct sockaddr_in6 *inet6addr;
190 
191 	switch (family) {
192 	case AF_INET:
193 		inetaddr = mbuf_data(nam);
194 		if ((inetaddr->sin_family == AF_INET) &&
195 		    (inetaddr->sin_addr.s_addr == haddr->had_inetaddr)) {
196 			return 1;
197 		}
198 		break;
199 	case AF_INET6:
200 		inet6addr = mbuf_data(nam);
201 		if ((inet6addr->sin6_family == AF_INET6) &&
202 		    !bcmp(&inet6addr->sin6_addr, &haddr->had_inet6addr, sizeof(inet6addr->sin6_addr))) {
203 			return 1;
204 		}
205 		break;
206 	}
207 	return 0;
208 }
209 
210 /*
211  * Look for the request in the cache
212  * If found then
213  *    return action and optionally reply
214  * else
215  *    insert it in the cache
216  *
217  * The rules are as follows:
218  * - if in progress, return DROP request
219  * - if completed within DELAY of the current time, return DROP it
220  * - if completed a longer time ago return REPLY if the reply was cached or
221  *   return DOIT
222  * Update/add new request at end of lru list
223  */
224 int
nfsrv_getcache(struct nfsrv_descript * nd,struct nfsrv_sock * slp,mbuf_t * mrepp)225 nfsrv_getcache(
226 	struct nfsrv_descript *nd,
227 	struct nfsrv_sock *slp,
228 	mbuf_t *mrepp)
229 {
230 	struct nfsrvcache *rp;
231 	struct nfsm_chain nmrep;
232 	struct sockaddr *saddr;
233 	int ret, error;
234 
235 	/*
236 	 * Don't cache recent requests for reliable transport protocols.
237 	 * (Maybe we should for the case of a reconnect, but..)
238 	 */
239 	if (!nd->nd_nam2) {
240 		return RC_DOIT;
241 	}
242 	lck_mtx_lock(&nfsrv_reqcache_mutex);
243 loop:
244 	for (rp = NFSRCHASH(nd->nd_retxid)->lh_first; rp != 0;
245 	    rp = rp->rc_hash.le_next) {
246 		if (nd->nd_retxid == rp->rc_xid && nd->nd_procnum == rp->rc_proc &&
247 		    netaddr_match(rp->rc_family, &rp->rc_haddr, nd->nd_nam)) {
248 			if ((rp->rc_flag & RC_LOCKED) != 0) {
249 				rp->rc_flag |= RC_WANTED;
250 				msleep(rp, &nfsrv_reqcache_mutex, PZERO - 1, "nfsrc", NULL);
251 				goto loop;
252 			}
253 			rp->rc_flag |= RC_LOCKED;
254 			/* If not at end of LRU chain, move it there */
255 			if (rp->rc_lru.tqe_next) {
256 				TAILQ_REMOVE(&nfsrv_reqcache_lruhead, rp, rc_lru);
257 				TAILQ_INSERT_TAIL(&nfsrv_reqcache_lruhead, rp, rc_lru);
258 			}
259 			if (rp->rc_state == RC_UNUSED) {
260 				panic("nfsrv cache");
261 			}
262 			if (rp->rc_state == RC_INPROG) {
263 				OSAddAtomic64(1, &nfsrvstats.srvcache_inproghits);
264 				ret = RC_DROPIT;
265 			} else if (rp->rc_flag & RC_REPSTATUS) {
266 				OSAddAtomic64(1, &nfsrvstats.srvcache_nonidemdonehits);
267 				nd->nd_repstat = rp->rc_status;
268 				error = nfsrv_rephead(nd, slp, &nmrep, 0);
269 				if (error) {
270 					printf("nfsrv cache: reply alloc failed for nonidem request hit\n");
271 					ret = RC_DROPIT;
272 					*mrepp = NULL;
273 				} else {
274 					ret = RC_REPLY;
275 					*mrepp = nmrep.nmc_mhead;
276 				}
277 			} else if (rp->rc_flag & RC_REPMBUF) {
278 				OSAddAtomic64(1, &nfsrvstats.srvcache_nonidemdonehits);
279 				error = mbuf_copym(rp->rc_reply, 0, MBUF_COPYALL, MBUF_WAITOK, mrepp);
280 				if (error) {
281 					printf("nfsrv cache: reply copym failed for nonidem request hit\n");
282 					ret = RC_DROPIT;
283 				} else {
284 					ret = RC_REPLY;
285 				}
286 			} else {
287 				OSAddAtomic64(1, &nfsrvstats.srvcache_idemdonehits);
288 				rp->rc_state = RC_INPROG;
289 				ret = RC_DOIT;
290 			}
291 			rp->rc_flag &= ~RC_LOCKED;
292 			if (rp->rc_flag & RC_WANTED) {
293 				rp->rc_flag &= ~RC_WANTED;
294 				wakeup(rp);
295 			}
296 			lck_mtx_unlock(&nfsrv_reqcache_mutex);
297 			return ret;
298 		}
299 	}
300 	OSAddAtomic64(1, &nfsrvstats.srvcache_misses);
301 	if (nfsrv_reqcache_count < nfsrv_reqcache_size) {
302 		/* try to allocate a new entry */
303 		MALLOC(rp, struct nfsrvcache *, sizeof *rp, M_NFSD, M_WAITOK);
304 		if (rp) {
305 			bzero((char *)rp, sizeof *rp);
306 			nfsrv_reqcache_count++;
307 			rp->rc_flag = RC_LOCKED;
308 		}
309 	} else {
310 		rp = NULL;
311 	}
312 	if (!rp) {
313 		/* try to reuse the least recently used entry */
314 		rp = nfsrv_reqcache_lruhead.tqh_first;
315 		if (!rp) {
316 			/* no entry to reuse? */
317 			/* OK, we just won't be able to cache this request */
318 			lck_mtx_unlock(&nfsrv_reqcache_mutex);
319 			return RC_DOIT;
320 		}
321 		while ((rp->rc_flag & RC_LOCKED) != 0) {
322 			rp->rc_flag |= RC_WANTED;
323 			msleep(rp, &nfsrv_reqcache_mutex, PZERO - 1, "nfsrc", NULL);
324 			rp = nfsrv_reqcache_lruhead.tqh_first;
325 		}
326 		rp->rc_flag |= RC_LOCKED;
327 		LIST_REMOVE(rp, rc_hash);
328 		TAILQ_REMOVE(&nfsrv_reqcache_lruhead, rp, rc_lru);
329 		if (rp->rc_flag & RC_REPMBUF) {
330 			mbuf_freem(rp->rc_reply);
331 		}
332 		if (rp->rc_flag & RC_NAM) {
333 			mbuf_freem(rp->rc_nam);
334 		}
335 		rp->rc_flag &= (RC_LOCKED | RC_WANTED);
336 	}
337 	TAILQ_INSERT_TAIL(&nfsrv_reqcache_lruhead, rp, rc_lru);
338 	rp->rc_state = RC_INPROG;
339 	rp->rc_xid = nd->nd_retxid;
340 	saddr = mbuf_data(nd->nd_nam);
341 	rp->rc_family = saddr->sa_family;
342 	switch (saddr->sa_family) {
343 	case AF_INET:
344 		rp->rc_flag |= RC_INETADDR;
345 		rp->rc_inetaddr = ((struct sockaddr_in*)saddr)->sin_addr.s_addr;
346 		break;
347 	case AF_INET6:
348 		rp->rc_flag |= RC_INETADDR;
349 		rp->rc_inet6addr = ((struct sockaddr_in6*)saddr)->sin6_addr;
350 		break;
351 	default:
352 		error = mbuf_copym(nd->nd_nam, 0, MBUF_COPYALL, MBUF_WAITOK, &rp->rc_nam);
353 		if (error) {
354 			printf("nfsrv cache: nam copym failed\n");
355 		} else {
356 			rp->rc_flag |= RC_NAM;
357 		}
358 		break;
359 	}
360 	;
361 	rp->rc_proc = nd->nd_procnum;
362 	LIST_INSERT_HEAD(NFSRCHASH(nd->nd_retxid), rp, rc_hash);
363 	rp->rc_flag &= ~RC_LOCKED;
364 	if (rp->rc_flag & RC_WANTED) {
365 		rp->rc_flag &= ~RC_WANTED;
366 		wakeup(rp);
367 	}
368 	lck_mtx_unlock(&nfsrv_reqcache_mutex);
369 	return RC_DOIT;
370 }
371 
372 /*
373  * Update a request cache entry after the rpc has been done
374  */
375 void
nfsrv_updatecache(struct nfsrv_descript * nd,int repvalid,mbuf_t repmbuf)376 nfsrv_updatecache(
377 	struct nfsrv_descript *nd,
378 	int repvalid,
379 	mbuf_t repmbuf)
380 {
381 	struct nfsrvcache *rp;
382 	int error;
383 
384 	if (!nd->nd_nam2) {
385 		return;
386 	}
387 	lck_mtx_lock(&nfsrv_reqcache_mutex);
388 loop:
389 	for (rp = NFSRCHASH(nd->nd_retxid)->lh_first; rp != 0;
390 	    rp = rp->rc_hash.le_next) {
391 		if (nd->nd_retxid == rp->rc_xid && nd->nd_procnum == rp->rc_proc &&
392 		    netaddr_match(rp->rc_family, &rp->rc_haddr, nd->nd_nam)) {
393 			if ((rp->rc_flag & RC_LOCKED) != 0) {
394 				rp->rc_flag |= RC_WANTED;
395 				msleep(rp, &nfsrv_reqcache_mutex, PZERO - 1, "nfsrc", NULL);
396 				goto loop;
397 			}
398 			rp->rc_flag |= RC_LOCKED;
399 			if (rp->rc_state == RC_DONE) {
400 				/*
401 				 * This can occur if the cache is too small.
402 				 * Retransmits of the same request aren't
403 				 * dropped so we may see the operation
404 				 * complete more then once.
405 				 */
406 				if (rp->rc_flag & RC_REPMBUF) {
407 					mbuf_freem(rp->rc_reply);
408 					rp->rc_flag &= ~RC_REPMBUF;
409 				}
410 			}
411 			rp->rc_state = RC_DONE;
412 			/*
413 			 * If we have a valid reply update status and save
414 			 * the reply for non-idempotent rpc's.
415 			 */
416 			if (repvalid && nonidempotent[nd->nd_procnum]) {
417 				if ((nd->nd_vers == NFS_VER2) &&
418 				    nfsv2_repstat[nfsv2_procid[nd->nd_procnum]]) {
419 					rp->rc_status = nd->nd_repstat;
420 					rp->rc_flag |= RC_REPSTATUS;
421 				} else {
422 					error = mbuf_copym(repmbuf, 0, MBUF_COPYALL, MBUF_WAITOK, &rp->rc_reply);
423 					if (!error) {
424 						rp->rc_flag |= RC_REPMBUF;
425 					}
426 				}
427 			}
428 			rp->rc_flag &= ~RC_LOCKED;
429 			if (rp->rc_flag & RC_WANTED) {
430 				rp->rc_flag &= ~RC_WANTED;
431 				wakeup(rp);
432 			}
433 			lck_mtx_unlock(&nfsrv_reqcache_mutex);
434 			return;
435 		}
436 	}
437 	lck_mtx_unlock(&nfsrv_reqcache_mutex);
438 }
439 
440 /*
441  * Clean out the cache. Called when the last nfsd terminates.
442  */
443 void
nfsrv_cleancache(void)444 nfsrv_cleancache(void)
445 {
446 	struct nfsrvcache *rp, *nextrp;
447 
448 	lck_mtx_lock(&nfsrv_reqcache_mutex);
449 	for (rp = nfsrv_reqcache_lruhead.tqh_first; rp != 0; rp = nextrp) {
450 		nextrp = rp->rc_lru.tqe_next;
451 		LIST_REMOVE(rp, rc_hash);
452 		TAILQ_REMOVE(&nfsrv_reqcache_lruhead, rp, rc_lru);
453 		_FREE(rp, M_NFSD);
454 	}
455 	nfsrv_reqcache_count = 0;
456 	FREE(nfsrv_reqcache_hashtbl, M_TEMP);
457 	lck_mtx_unlock(&nfsrv_reqcache_mutex);
458 }
459 
460 #endif /* CONFIG_NFS_SERVER */
461