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