xref: /xnu-11417.140.69/bsd/nfs/nfs_srvcache.c (revision 43a90889846e00bfb5cf1d255cdc0a701a1e05a4)
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/mbuf.h>
83 #include <sys/kpi_mbuf.h>
84 #include <sys/malloc.h>
85 #include <sys/socket.h>
86 #include <libkern/OSAtomic.h>
87 
88 #include <netinet/in.h>
89 #include <nfs/rpcv2.h>
90 #include <nfs/nfsproto.h>
91 #include <nfs/nfs.h>
92 #include <nfs/nfsrvcache.h>
93 
94 extern int nfsv2_procid[NFS_NPROCS];
95 static int nfsrv_reqcache_count;
96 int nfsrv_reqcache_size = NFSRVCACHESIZ;
97 
98 #define NFSRCHASH(xid) \
99 	(&nfsrv_reqcache_hashtbl[((xid) + ((xid) >> 24)) & nfsrv_reqcache_hash])
100 static LIST_HEAD(nfsrv_reqcache_hash, nfsrvcache) * nfsrv_reqcache_hashtbl;
101 static TAILQ_HEAD(nfsrv_reqcache_lru, nfsrvcache) nfsrv_reqcache_lruhead;
102 static u_long nfsrv_reqcache_hash;
103 
104 static LCK_GRP_DECLARE(nfsrv_reqcache_lck_grp, "nfsrv_reqcache");
105 static LCK_MTX_DECLARE(nfsrv_reqcache_mutex, &nfsrv_reqcache_lck_grp);
106 
107 /*
108  * Static array that defines which nfs rpc's are nonidempotent
109  */
110 static int nonidempotent[NFS_NPROCS] = {
111 	FALSE,
112 	FALSE,
113 	TRUE,
114 	FALSE,
115 	FALSE,
116 	FALSE,
117 	FALSE,
118 	TRUE,
119 	TRUE,
120 	TRUE,
121 	TRUE,
122 	TRUE,
123 	TRUE,
124 	TRUE,
125 	TRUE,
126 	TRUE,
127 	FALSE,
128 	FALSE,
129 	FALSE,
130 	FALSE,
131 	FALSE,
132 	FALSE,
133 	FALSE,
134 };
135 
136 /* True iff the rpc reply is an nfs status ONLY! */
137 static int nfsv2_repstat[NFS_NPROCS] = {
138 	FALSE,
139 	FALSE,
140 	FALSE,
141 	FALSE,
142 	FALSE,
143 	FALSE,
144 	FALSE,
145 	FALSE,
146 	FALSE,
147 	FALSE,
148 	TRUE,
149 	TRUE,
150 	TRUE,
151 	TRUE,
152 	FALSE,
153 	TRUE,
154 	FALSE,
155 	FALSE,
156 };
157 
158 /*
159  * Initialize the server request cache list
160  */
161 void
nfsrv_initcache(void)162 nfsrv_initcache(void)
163 {
164 	if (nfsrv_reqcache_size <= 0) {
165 		return;
166 	}
167 
168 	lck_mtx_lock(&nfsrv_reqcache_mutex);
169 	/* init nfs server request cache hash table */
170 	nfsrv_reqcache_hashtbl = hashinit(nfsrv_reqcache_size, M_NFSD, &nfsrv_reqcache_hash);
171 	TAILQ_INIT(&nfsrv_reqcache_lruhead);
172 	lck_mtx_unlock(&nfsrv_reqcache_mutex);
173 }
174 
175 /*
176  * This function compares two net addresses by family and returns TRUE
177  * if they are the same host.
178  * If there is any doubt, return FALSE.
179  * The AF_INET family is handled as a special case so that address mbufs
180  * don't need to be saved to store "struct in_addr", which is only 4 bytes.
181  * Ditto for AF_INET6 which is only 16 bytes.
182  */
183 static int
netaddr_match(int family,union nethostaddr * haddr,mbuf_t nam)184 netaddr_match(
185 	int family,
186 	union nethostaddr *haddr,
187 	mbuf_t nam)
188 {
189 	struct sockaddr_in *inetaddr;
190 	struct sockaddr_in6 *inet6addr;
191 
192 	switch (family) {
193 	case AF_INET:
194 		inetaddr = SIN(mtod(nam, caddr_t));
195 		if ((inetaddr->sin_family == AF_INET) &&
196 		    (inetaddr->sin_addr.s_addr == haddr->had_inetaddr)) {
197 			return 1;
198 		}
199 		break;
200 	case AF_INET6:
201 		inet6addr = SIN6(mtod(nam, caddr_t));
202 		if ((inet6addr->sin6_family == AF_INET6) &&
203 		    !bcmp(&inet6addr->sin6_addr, &haddr->had_inet6addr, sizeof(inet6addr->sin6_addr))) {
204 			return 1;
205 		}
206 		break;
207 	}
208 	return 0;
209 }
210 
211 /*
212  * Look for the request in the cache
213  * If found then
214  *    return action and optionally reply
215  * else
216  *    insert it in the cache
217  *
218  * The rules are as follows:
219  * - if in progress, return DROP request
220  * - if completed within DELAY of the current time, return DROP it
221  * - if completed a longer time ago return REPLY if the reply was cached or
222  *   return DOIT
223  * Update/add new request at end of lru list
224  */
225 int
nfsrv_getcache(struct nfsrv_descript * nd,struct nfsrv_sock * slp,mbuf_t * mrepp)226 nfsrv_getcache(
227 	struct nfsrv_descript *nd,
228 	struct nfsrv_sock *slp,
229 	mbuf_t *mrepp)
230 {
231 	struct nfsrvcache *rp;
232 	struct nfsm_chain nmrep;
233 	struct sockaddr *saddr;
234 	int ret, error;
235 
236 	/*
237 	 * Don't cache recent requests for reliable transport protocols.
238 	 * (Maybe we should for the case of a reconnect, but..)
239 	 */
240 	if (!nd->nd_nam2) {
241 		return RC_DOIT;
242 	}
243 	lck_mtx_lock(&nfsrv_reqcache_mutex);
244 loop:
245 	for (rp = NFSRCHASH(nd->nd_retxid)->lh_first; rp != 0;
246 	    rp = rp->rc_hash.le_next) {
247 		if (nd->nd_retxid == rp->rc_xid && nd->nd_procnum == rp->rc_proc &&
248 		    netaddr_match(rp->rc_family, &rp->rc_haddr, nd->nd_nam)) {
249 			if ((rp->rc_flag & RC_LOCKED) != 0) {
250 				rp->rc_flag |= RC_WANTED;
251 				msleep(rp, &nfsrv_reqcache_mutex, PZERO - 1, "nfsrc", NULL);
252 				goto loop;
253 			}
254 			rp->rc_flag |= RC_LOCKED;
255 			/* If not at end of LRU chain, move it there */
256 			if (rp->rc_lru.tqe_next) {
257 				TAILQ_REMOVE(&nfsrv_reqcache_lruhead, rp, rc_lru);
258 				TAILQ_INSERT_TAIL(&nfsrv_reqcache_lruhead, rp, rc_lru);
259 			}
260 			if (rp->rc_state == RC_UNUSED) {
261 				panic("nfsrv cache");
262 			}
263 			if (rp->rc_state == RC_INPROG) {
264 				OSAddAtomic64(1, &nfsrvstats.srvcache_inproghits);
265 				ret = RC_DROPIT;
266 			} else if (rp->rc_flag & RC_REPSTATUS) {
267 				OSAddAtomic64(1, &nfsrvstats.srvcache_nonidemdonehits);
268 				nd->nd_repstat = rp->rc_status;
269 				error = nfsrv_rephead(nd, slp, &nmrep, 0);
270 				if (error) {
271 					printf("nfsrv cache: reply alloc failed for nonidem request hit\n");
272 					ret = RC_DROPIT;
273 					*mrepp = NULL;
274 				} else {
275 					ret = RC_REPLY;
276 					*mrepp = nmrep.nmc_mhead;
277 				}
278 			} else if (rp->rc_flag & RC_REPMBUF) {
279 				OSAddAtomic64(1, &nfsrvstats.srvcache_nonidemdonehits);
280 				error = mbuf_copym(rp->rc_reply, 0, MBUF_COPYALL, MBUF_WAITOK, mrepp);
281 				if (error) {
282 					printf("nfsrv cache: reply copym failed for nonidem request hit\n");
283 					ret = RC_DROPIT;
284 				} else {
285 					ret = RC_REPLY;
286 				}
287 			} else {
288 				OSAddAtomic64(1, &nfsrvstats.srvcache_idemdonehits);
289 				rp->rc_state = RC_INPROG;
290 				ret = RC_DOIT;
291 			}
292 			rp->rc_flag &= ~RC_LOCKED;
293 			if (rp->rc_flag & RC_WANTED) {
294 				rp->rc_flag &= ~RC_WANTED;
295 				wakeup(rp);
296 			}
297 			lck_mtx_unlock(&nfsrv_reqcache_mutex);
298 			return ret;
299 		}
300 	}
301 	OSAddAtomic64(1, &nfsrvstats.srvcache_misses);
302 	if (nfsrv_reqcache_count < nfsrv_reqcache_size) {
303 		/* try to allocate a new entry */
304 		rp = kalloc_type(struct nfsrvcache, Z_WAITOK | Z_ZERO | Z_NOFAIL);
305 		rp->rc_flag = RC_LOCKED;
306 		nfsrv_reqcache_count++;
307 	} else {
308 		rp = NULL;
309 	}
310 	if (!rp) {
311 		/* try to reuse the least recently used entry */
312 		rp = nfsrv_reqcache_lruhead.tqh_first;
313 		if (!rp) {
314 			/* no entry to reuse? */
315 			/* OK, we just won't be able to cache this request */
316 			lck_mtx_unlock(&nfsrv_reqcache_mutex);
317 			return RC_DOIT;
318 		}
319 		while ((rp->rc_flag & RC_LOCKED) != 0) {
320 			rp->rc_flag |= RC_WANTED;
321 			msleep(rp, &nfsrv_reqcache_mutex, PZERO - 1, "nfsrc", NULL);
322 			rp = nfsrv_reqcache_lruhead.tqh_first;
323 		}
324 		rp->rc_flag |= RC_LOCKED;
325 		LIST_REMOVE(rp, rc_hash);
326 		TAILQ_REMOVE(&nfsrv_reqcache_lruhead, rp, rc_lru);
327 		if (rp->rc_flag & RC_REPMBUF) {
328 			mbuf_freem(rp->rc_reply);
329 		}
330 		if (rp->rc_flag & RC_NAM) {
331 			mbuf_freem(rp->rc_nam);
332 		}
333 		rp->rc_flag &= (RC_LOCKED | RC_WANTED);
334 	}
335 	TAILQ_INSERT_TAIL(&nfsrv_reqcache_lruhead, rp, rc_lru);
336 	rp->rc_state = RC_INPROG;
337 	rp->rc_xid = nd->nd_retxid;
338 	saddr = SA(mtod(nd->nd_nam, caddr_t));
339 	rp->rc_family = saddr->sa_family;
340 	switch (saddr->sa_family) {
341 	case AF_INET:
342 		rp->rc_flag |= RC_INETADDR;
343 		rp->rc_inetaddr = ((struct sockaddr_in*)saddr)->sin_addr.s_addr;
344 		break;
345 	case AF_INET6:
346 		rp->rc_flag |= RC_INETADDR;
347 		rp->rc_inet6addr = ((struct sockaddr_in6*)saddr)->sin6_addr;
348 		break;
349 	default:
350 		error = mbuf_copym(nd->nd_nam, 0, MBUF_COPYALL, MBUF_WAITOK, &rp->rc_nam);
351 		if (error) {
352 			printf("nfsrv cache: nam copym failed\n");
353 		} else {
354 			rp->rc_flag |= RC_NAM;
355 		}
356 		break;
357 	}
358 	;
359 	rp->rc_proc = nd->nd_procnum;
360 	LIST_INSERT_HEAD(NFSRCHASH(nd->nd_retxid), rp, rc_hash);
361 	rp->rc_flag &= ~RC_LOCKED;
362 	if (rp->rc_flag & RC_WANTED) {
363 		rp->rc_flag &= ~RC_WANTED;
364 		wakeup(rp);
365 	}
366 	lck_mtx_unlock(&nfsrv_reqcache_mutex);
367 	return RC_DOIT;
368 }
369 
370 /*
371  * Update a request cache entry after the rpc has been done
372  */
373 void
nfsrv_updatecache(struct nfsrv_descript * nd,int repvalid,mbuf_t repmbuf)374 nfsrv_updatecache(
375 	struct nfsrv_descript *nd,
376 	int repvalid,
377 	mbuf_t repmbuf)
378 {
379 	struct nfsrvcache *rp;
380 	int error;
381 
382 	if (!nd->nd_nam2) {
383 		return;
384 	}
385 	lck_mtx_lock(&nfsrv_reqcache_mutex);
386 loop:
387 	for (rp = NFSRCHASH(nd->nd_retxid)->lh_first; rp != 0;
388 	    rp = rp->rc_hash.le_next) {
389 		if (nd->nd_retxid == rp->rc_xid && nd->nd_procnum == rp->rc_proc &&
390 		    netaddr_match(rp->rc_family, &rp->rc_haddr, nd->nd_nam)) {
391 			if ((rp->rc_flag & RC_LOCKED) != 0) {
392 				rp->rc_flag |= RC_WANTED;
393 				msleep(rp, &nfsrv_reqcache_mutex, PZERO - 1, "nfsrc", NULL);
394 				goto loop;
395 			}
396 			rp->rc_flag |= RC_LOCKED;
397 			if (rp->rc_state == RC_DONE) {
398 				/*
399 				 * This can occur if the cache is too small.
400 				 * Retransmits of the same request aren't
401 				 * dropped so we may see the operation
402 				 * complete more then once.
403 				 */
404 				if (rp->rc_flag & RC_REPMBUF) {
405 					mbuf_freem(rp->rc_reply);
406 					rp->rc_flag &= ~RC_REPMBUF;
407 				}
408 			}
409 			rp->rc_state = RC_DONE;
410 			/*
411 			 * If we have a valid reply update status and save
412 			 * the reply for non-idempotent rpc's.
413 			 */
414 			if (repvalid && nonidempotent[nd->nd_procnum]) {
415 				if ((nd->nd_vers == NFS_VER2) &&
416 				    nfsv2_repstat[nfsv2_procid[nd->nd_procnum]]) {
417 					rp->rc_status = nd->nd_repstat;
418 					rp->rc_flag |= RC_REPSTATUS;
419 				} else {
420 					error = mbuf_copym(repmbuf, 0, MBUF_COPYALL, MBUF_WAITOK, &rp->rc_reply);
421 					if (!error) {
422 						rp->rc_flag |= RC_REPMBUF;
423 					}
424 				}
425 			}
426 			rp->rc_flag &= ~RC_LOCKED;
427 			if (rp->rc_flag & RC_WANTED) {
428 				rp->rc_flag &= ~RC_WANTED;
429 				wakeup(rp);
430 			}
431 			lck_mtx_unlock(&nfsrv_reqcache_mutex);
432 			return;
433 		}
434 	}
435 	lck_mtx_unlock(&nfsrv_reqcache_mutex);
436 }
437 
438 /*
439  * Clean out the cache. Called when the last nfsd terminates.
440  */
441 void
nfsrv_cleancache(void)442 nfsrv_cleancache(void)
443 {
444 	struct nfsrvcache *rp, *nextrp;
445 
446 	lck_mtx_lock(&nfsrv_reqcache_mutex);
447 	TAILQ_FOREACH_SAFE(rp, &nfsrv_reqcache_lruhead, rc_lru, nextrp) {
448 		kfree_type(struct nfsrvcache, rp);
449 	}
450 	hashdestroy(nfsrv_reqcache_hashtbl, M_NFSD, nfsrv_reqcache_hash);
451 	nfsrv_reqcache_hash = 0;
452 	nfsrv_reqcache_count = 0;
453 	TAILQ_INIT(&nfsrv_reqcache_lruhead);
454 	lck_mtx_unlock(&nfsrv_reqcache_mutex);
455 }
456 
457 #endif /* CONFIG_NFS_SERVER */
458