]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Request reply cache. This is currently a global cache, but this may | |
3 | * change in the future and be a per-client cache. | |
4 | * | |
5 | * This code is heavily inspired by the 44BSD implementation, although | |
6 | * it does things a bit differently. | |
7 | * | |
8 | * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> | |
9 | */ | |
10 | ||
11 | #include <linux/slab.h> | |
12 | #include <linux/sunrpc/clnt.h> | |
13 | ||
14 | #include "nfsd.h" | |
15 | #include "cache.h" | |
16 | ||
17 | /* Size of reply cache. Common values are: | |
18 | * 4.3BSD: 128 | |
19 | * 4.4BSD: 256 | |
20 | * Solaris2: 1024 | |
21 | * DEC Unix: 512-4096 | |
22 | */ | |
23 | #define CACHESIZE 1024 | |
24 | #define HASHSIZE 64 | |
25 | ||
26 | static struct hlist_head * cache_hash; | |
27 | static struct list_head lru_head; | |
28 | static int cache_disabled = 1; | |
29 | static struct kmem_cache *drc_slab; | |
30 | ||
31 | /* | |
32 | * Calculate the hash index from an XID. | |
33 | */ | |
34 | static inline u32 request_hash(u32 xid) | |
35 | { | |
36 | u32 h = xid; | |
37 | h ^= (xid >> 24); | |
38 | return h & (HASHSIZE-1); | |
39 | } | |
40 | ||
41 | static int nfsd_cache_append(struct svc_rqst *rqstp, struct kvec *vec); | |
42 | ||
43 | /* | |
44 | * locking for the reply cache: | |
45 | * A cache entry is "single use" if c_state == RC_INPROG | |
46 | * Otherwise, it when accessing _prev or _next, the lock must be held. | |
47 | */ | |
48 | static DEFINE_SPINLOCK(cache_lock); | |
49 | ||
50 | static struct svc_cacherep * | |
51 | nfsd_reply_cache_alloc(void) | |
52 | { | |
53 | struct svc_cacherep *rp; | |
54 | ||
55 | rp = kmem_cache_alloc(drc_slab, GFP_KERNEL); | |
56 | if (rp) { | |
57 | rp->c_state = RC_UNUSED; | |
58 | rp->c_type = RC_NOCACHE; | |
59 | INIT_LIST_HEAD(&rp->c_lru); | |
60 | INIT_HLIST_NODE(&rp->c_hash); | |
61 | } | |
62 | return rp; | |
63 | } | |
64 | ||
65 | static void | |
66 | nfsd_reply_cache_free_locked(struct svc_cacherep *rp) | |
67 | { | |
68 | if (rp->c_state == RC_DONE && rp->c_type == RC_REPLBUFF) | |
69 | kfree(rp->c_replvec.iov_base); | |
70 | list_del(&rp->c_lru); | |
71 | kmem_cache_free(drc_slab, rp); | |
72 | } | |
73 | ||
74 | int nfsd_reply_cache_init(void) | |
75 | { | |
76 | int i; | |
77 | struct svc_cacherep *rp; | |
78 | ||
79 | drc_slab = kmem_cache_create("nfsd_drc", sizeof(struct svc_cacherep), | |
80 | 0, 0, NULL); | |
81 | if (!drc_slab) | |
82 | goto out_nomem; | |
83 | ||
84 | INIT_LIST_HEAD(&lru_head); | |
85 | i = CACHESIZE; | |
86 | while (i) { | |
87 | rp = nfsd_reply_cache_alloc(); | |
88 | if (!rp) | |
89 | goto out_nomem; | |
90 | list_add(&rp->c_lru, &lru_head); | |
91 | i--; | |
92 | } | |
93 | ||
94 | cache_hash = kcalloc (HASHSIZE, sizeof(struct hlist_head), GFP_KERNEL); | |
95 | if (!cache_hash) | |
96 | goto out_nomem; | |
97 | ||
98 | cache_disabled = 0; | |
99 | return 0; | |
100 | out_nomem: | |
101 | printk(KERN_ERR "nfsd: failed to allocate reply cache\n"); | |
102 | nfsd_reply_cache_shutdown(); | |
103 | return -ENOMEM; | |
104 | } | |
105 | ||
106 | void nfsd_reply_cache_shutdown(void) | |
107 | { | |
108 | struct svc_cacherep *rp; | |
109 | ||
110 | while (!list_empty(&lru_head)) { | |
111 | rp = list_entry(lru_head.next, struct svc_cacherep, c_lru); | |
112 | nfsd_reply_cache_free_locked(rp); | |
113 | } | |
114 | ||
115 | cache_disabled = 1; | |
116 | ||
117 | kfree (cache_hash); | |
118 | cache_hash = NULL; | |
119 | ||
120 | if (drc_slab) { | |
121 | kmem_cache_destroy(drc_slab); | |
122 | drc_slab = NULL; | |
123 | } | |
124 | } | |
125 | ||
126 | /* | |
127 | * Move cache entry to end of LRU list | |
128 | */ | |
129 | static void | |
130 | lru_put_end(struct svc_cacherep *rp) | |
131 | { | |
132 | list_move_tail(&rp->c_lru, &lru_head); | |
133 | } | |
134 | ||
135 | /* | |
136 | * Move a cache entry from one hash list to another | |
137 | */ | |
138 | static void | |
139 | hash_refile(struct svc_cacherep *rp) | |
140 | { | |
141 | hlist_del_init(&rp->c_hash); | |
142 | hlist_add_head(&rp->c_hash, cache_hash + request_hash(rp->c_xid)); | |
143 | } | |
144 | ||
145 | /* | |
146 | * Try to find an entry matching the current call in the cache. When none | |
147 | * is found, we grab the oldest unlocked entry off the LRU list. | |
148 | * Note that no operation within the loop may sleep. | |
149 | */ | |
150 | int | |
151 | nfsd_cache_lookup(struct svc_rqst *rqstp) | |
152 | { | |
153 | struct hlist_node *hn; | |
154 | struct hlist_head *rh; | |
155 | struct svc_cacherep *rp; | |
156 | __be32 xid = rqstp->rq_xid; | |
157 | u32 proto = rqstp->rq_prot, | |
158 | vers = rqstp->rq_vers, | |
159 | proc = rqstp->rq_proc; | |
160 | unsigned long age; | |
161 | int type = rqstp->rq_cachetype; | |
162 | int rtn; | |
163 | ||
164 | rqstp->rq_cacherep = NULL; | |
165 | if (cache_disabled || type == RC_NOCACHE) { | |
166 | nfsdstats.rcnocache++; | |
167 | return RC_DOIT; | |
168 | } | |
169 | ||
170 | spin_lock(&cache_lock); | |
171 | rtn = RC_DOIT; | |
172 | ||
173 | rh = &cache_hash[request_hash(xid)]; | |
174 | hlist_for_each_entry(rp, hn, rh, c_hash) { | |
175 | if (rp->c_state != RC_UNUSED && | |
176 | xid == rp->c_xid && proc == rp->c_proc && | |
177 | proto == rp->c_prot && vers == rp->c_vers && | |
178 | time_before(jiffies, rp->c_timestamp + 120*HZ) && | |
179 | rpc_cmp_addr(svc_addr(rqstp), (struct sockaddr *)&rp->c_addr) && | |
180 | rpc_get_port(svc_addr(rqstp)) == rpc_get_port((struct sockaddr *)&rp->c_addr)) { | |
181 | nfsdstats.rchits++; | |
182 | goto found_entry; | |
183 | } | |
184 | } | |
185 | nfsdstats.rcmisses++; | |
186 | ||
187 | /* This loop shouldn't take more than a few iterations normally */ | |
188 | { | |
189 | int safe = 0; | |
190 | list_for_each_entry(rp, &lru_head, c_lru) { | |
191 | if (rp->c_state != RC_INPROG) | |
192 | break; | |
193 | if (safe++ > CACHESIZE) { | |
194 | printk("nfsd: loop in repcache LRU list\n"); | |
195 | cache_disabled = 1; | |
196 | goto out; | |
197 | } | |
198 | } | |
199 | } | |
200 | ||
201 | /* All entries on the LRU are in-progress. This should not happen */ | |
202 | if (&rp->c_lru == &lru_head) { | |
203 | static int complaints; | |
204 | ||
205 | printk(KERN_WARNING "nfsd: all repcache entries locked!\n"); | |
206 | if (++complaints > 5) { | |
207 | printk(KERN_WARNING "nfsd: disabling repcache.\n"); | |
208 | cache_disabled = 1; | |
209 | } | |
210 | goto out; | |
211 | } | |
212 | ||
213 | rqstp->rq_cacherep = rp; | |
214 | rp->c_state = RC_INPROG; | |
215 | rp->c_xid = xid; | |
216 | rp->c_proc = proc; | |
217 | rpc_copy_addr((struct sockaddr *)&rp->c_addr, svc_addr(rqstp)); | |
218 | rpc_set_port((struct sockaddr *)&rp->c_addr, rpc_get_port(svc_addr(rqstp))); | |
219 | rp->c_prot = proto; | |
220 | rp->c_vers = vers; | |
221 | rp->c_timestamp = jiffies; | |
222 | ||
223 | hash_refile(rp); | |
224 | ||
225 | /* release any buffer */ | |
226 | if (rp->c_type == RC_REPLBUFF) { | |
227 | kfree(rp->c_replvec.iov_base); | |
228 | rp->c_replvec.iov_base = NULL; | |
229 | } | |
230 | rp->c_type = RC_NOCACHE; | |
231 | out: | |
232 | spin_unlock(&cache_lock); | |
233 | return rtn; | |
234 | ||
235 | found_entry: | |
236 | /* We found a matching entry which is either in progress or done. */ | |
237 | age = jiffies - rp->c_timestamp; | |
238 | rp->c_timestamp = jiffies; | |
239 | lru_put_end(rp); | |
240 | ||
241 | rtn = RC_DROPIT; | |
242 | /* Request being processed or excessive rexmits */ | |
243 | if (rp->c_state == RC_INPROG || age < RC_DELAY) | |
244 | goto out; | |
245 | ||
246 | /* From the hall of fame of impractical attacks: | |
247 | * Is this a user who tries to snoop on the cache? */ | |
248 | rtn = RC_DOIT; | |
249 | if (!rqstp->rq_secure && rp->c_secure) | |
250 | goto out; | |
251 | ||
252 | /* Compose RPC reply header */ | |
253 | switch (rp->c_type) { | |
254 | case RC_NOCACHE: | |
255 | break; | |
256 | case RC_REPLSTAT: | |
257 | svc_putu32(&rqstp->rq_res.head[0], rp->c_replstat); | |
258 | rtn = RC_REPLY; | |
259 | break; | |
260 | case RC_REPLBUFF: | |
261 | if (!nfsd_cache_append(rqstp, &rp->c_replvec)) | |
262 | goto out; /* should not happen */ | |
263 | rtn = RC_REPLY; | |
264 | break; | |
265 | default: | |
266 | printk(KERN_WARNING "nfsd: bad repcache type %d\n", rp->c_type); | |
267 | rp->c_state = RC_UNUSED; | |
268 | } | |
269 | ||
270 | goto out; | |
271 | } | |
272 | ||
273 | /* | |
274 | * Update a cache entry. This is called from nfsd_dispatch when | |
275 | * the procedure has been executed and the complete reply is in | |
276 | * rqstp->rq_res. | |
277 | * | |
278 | * We're copying around data here rather than swapping buffers because | |
279 | * the toplevel loop requires max-sized buffers, which would be a waste | |
280 | * of memory for a cache with a max reply size of 100 bytes (diropokres). | |
281 | * | |
282 | * If we should start to use different types of cache entries tailored | |
283 | * specifically for attrstat and fh's, we may save even more space. | |
284 | * | |
285 | * Also note that a cachetype of RC_NOCACHE can legally be passed when | |
286 | * nfsd failed to encode a reply that otherwise would have been cached. | |
287 | * In this case, nfsd_cache_update is called with statp == NULL. | |
288 | */ | |
289 | void | |
290 | nfsd_cache_update(struct svc_rqst *rqstp, int cachetype, __be32 *statp) | |
291 | { | |
292 | struct svc_cacherep *rp; | |
293 | struct kvec *resv = &rqstp->rq_res.head[0], *cachv; | |
294 | int len; | |
295 | ||
296 | if (!(rp = rqstp->rq_cacherep) || cache_disabled) | |
297 | return; | |
298 | ||
299 | len = resv->iov_len - ((char*)statp - (char*)resv->iov_base); | |
300 | len >>= 2; | |
301 | ||
302 | /* Don't cache excessive amounts of data and XDR failures */ | |
303 | if (!statp || len > (256 >> 2)) { | |
304 | rp->c_state = RC_UNUSED; | |
305 | return; | |
306 | } | |
307 | ||
308 | switch (cachetype) { | |
309 | case RC_REPLSTAT: | |
310 | if (len != 1) | |
311 | printk("nfsd: RC_REPLSTAT/reply len %d!\n",len); | |
312 | rp->c_replstat = *statp; | |
313 | break; | |
314 | case RC_REPLBUFF: | |
315 | cachv = &rp->c_replvec; | |
316 | cachv->iov_base = kmalloc(len << 2, GFP_KERNEL); | |
317 | if (!cachv->iov_base) { | |
318 | rp->c_state = RC_UNUSED; | |
319 | return; | |
320 | } | |
321 | cachv->iov_len = len << 2; | |
322 | memcpy(cachv->iov_base, statp, len << 2); | |
323 | break; | |
324 | } | |
325 | spin_lock(&cache_lock); | |
326 | lru_put_end(rp); | |
327 | rp->c_secure = rqstp->rq_secure; | |
328 | rp->c_type = cachetype; | |
329 | rp->c_state = RC_DONE; | |
330 | rp->c_timestamp = jiffies; | |
331 | spin_unlock(&cache_lock); | |
332 | return; | |
333 | } | |
334 | ||
335 | /* | |
336 | * Copy cached reply to current reply buffer. Should always fit. | |
337 | * FIXME as reply is in a page, we should just attach the page, and | |
338 | * keep a refcount.... | |
339 | */ | |
340 | static int | |
341 | nfsd_cache_append(struct svc_rqst *rqstp, struct kvec *data) | |
342 | { | |
343 | struct kvec *vec = &rqstp->rq_res.head[0]; | |
344 | ||
345 | if (vec->iov_len + data->iov_len > PAGE_SIZE) { | |
346 | printk(KERN_WARNING "nfsd: cached reply too large (%Zd).\n", | |
347 | data->iov_len); | |
348 | return 0; | |
349 | } | |
350 | memcpy((char*)vec->iov_base + vec->iov_len, data->iov_base, data->iov_len); | |
351 | vec->iov_len += data->iov_len; | |
352 | return 1; | |
353 | } |