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