Commit | Line | Data |
---|---|---|
60e78d2c AK |
1 | /* |
2 | * V9FS cache definitions. | |
3 | * | |
4 | * Copyright (C) 2009 by Abhishek Kulkarni <adkulkar@umail.iu.edu> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 | |
8 | * as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program; if not, write to: | |
17 | * Free Software Foundation | |
18 | * 51 Franklin Street, Fifth Floor | |
19 | * Boston, MA 02111-1301 USA | |
20 | * | |
21 | */ | |
22 | ||
23 | #include <linux/jiffies.h> | |
24 | #include <linux/file.h> | |
25 | #include <linux/stat.h> | |
26 | #include <linux/sched.h> | |
27 | #include <linux/fs.h> | |
28 | #include <net/9p/9p.h> | |
29 | ||
30 | #include "v9fs.h" | |
31 | #include "cache.h" | |
32 | ||
33 | #define CACHETAG_LEN 11 | |
34 | ||
35 | struct kmem_cache *vcookie_cache; | |
36 | ||
37 | struct fscache_netfs v9fs_cache_netfs = { | |
38 | .name = "9p", | |
39 | .version = 0, | |
40 | }; | |
41 | ||
42 | static void init_once(void *foo) | |
43 | { | |
44 | struct v9fs_cookie *vcookie = (struct v9fs_cookie *) foo; | |
45 | vcookie->fscache = NULL; | |
46 | vcookie->qid = NULL; | |
47 | inode_init_once(&vcookie->inode); | |
48 | } | |
49 | ||
50 | /** | |
51 | * v9fs_init_vcookiecache - initialize a cache for vcookies to maintain | |
52 | * vcookie to inode mapping | |
53 | * | |
54 | * Returns 0 on success. | |
55 | */ | |
56 | ||
57 | static int v9fs_init_vcookiecache(void) | |
58 | { | |
59 | vcookie_cache = kmem_cache_create("vcookie_cache", | |
60 | sizeof(struct v9fs_cookie), | |
61 | 0, (SLAB_RECLAIM_ACCOUNT| | |
62 | SLAB_MEM_SPREAD), | |
63 | init_once); | |
64 | if (!vcookie_cache) | |
65 | return -ENOMEM; | |
66 | ||
67 | return 0; | |
68 | } | |
69 | ||
70 | /** | |
71 | * v9fs_destroy_vcookiecache - destroy the cache of vcookies | |
72 | * | |
73 | */ | |
74 | ||
75 | static void v9fs_destroy_vcookiecache(void) | |
76 | { | |
77 | kmem_cache_destroy(vcookie_cache); | |
78 | } | |
79 | ||
80 | int __v9fs_cache_register(void) | |
81 | { | |
82 | int ret; | |
83 | ret = v9fs_init_vcookiecache(); | |
84 | if (ret < 0) | |
85 | return ret; | |
86 | ||
87 | return fscache_register_netfs(&v9fs_cache_netfs); | |
88 | } | |
89 | ||
90 | void __v9fs_cache_unregister(void) | |
91 | { | |
92 | v9fs_destroy_vcookiecache(); | |
93 | fscache_unregister_netfs(&v9fs_cache_netfs); | |
94 | } | |
95 | ||
96 | /** | |
97 | * v9fs_random_cachetag - Generate a random tag to be associated | |
98 | * with a new cache session. | |
99 | * | |
100 | * The value of jiffies is used for a fairly randomly cache tag. | |
101 | */ | |
102 | ||
103 | static | |
104 | int v9fs_random_cachetag(struct v9fs_session_info *v9ses) | |
105 | { | |
106 | v9ses->cachetag = kmalloc(CACHETAG_LEN, GFP_KERNEL); | |
107 | if (!v9ses->cachetag) | |
108 | return -ENOMEM; | |
109 | ||
110 | return scnprintf(v9ses->cachetag, CACHETAG_LEN, "%lu", jiffies); | |
111 | } | |
112 | ||
113 | static uint16_t v9fs_cache_session_get_key(const void *cookie_netfs_data, | |
114 | void *buffer, uint16_t bufmax) | |
115 | { | |
116 | struct v9fs_session_info *v9ses; | |
117 | uint16_t klen = 0; | |
118 | ||
119 | v9ses = (struct v9fs_session_info *)cookie_netfs_data; | |
120 | P9_DPRINTK(P9_DEBUG_FSC, "session %p buf %p size %u", v9ses, | |
121 | buffer, bufmax); | |
122 | ||
123 | if (v9ses->cachetag) | |
124 | klen = strlen(v9ses->cachetag); | |
125 | ||
126 | if (klen > bufmax) | |
127 | return 0; | |
128 | ||
129 | memcpy(buffer, v9ses->cachetag, klen); | |
130 | P9_DPRINTK(P9_DEBUG_FSC, "cache session tag %s", v9ses->cachetag); | |
131 | return klen; | |
132 | } | |
133 | ||
134 | const struct fscache_cookie_def v9fs_cache_session_index_def = { | |
135 | .name = "9P.session", | |
136 | .type = FSCACHE_COOKIE_TYPE_INDEX, | |
137 | .get_key = v9fs_cache_session_get_key, | |
138 | }; | |
139 | ||
140 | void v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses) | |
141 | { | |
142 | /* If no cache session tag was specified, we generate a random one. */ | |
143 | if (!v9ses->cachetag) | |
144 | v9fs_random_cachetag(v9ses); | |
145 | ||
146 | v9ses->fscache = fscache_acquire_cookie(v9fs_cache_netfs.primary_index, | |
147 | &v9fs_cache_session_index_def, | |
148 | v9ses); | |
149 | P9_DPRINTK(P9_DEBUG_FSC, "session %p get cookie %p", v9ses, | |
150 | v9ses->fscache); | |
151 | } | |
152 | ||
153 | void v9fs_cache_session_put_cookie(struct v9fs_session_info *v9ses) | |
154 | { | |
155 | P9_DPRINTK(P9_DEBUG_FSC, "session %p put cookie %p", v9ses, | |
156 | v9ses->fscache); | |
157 | fscache_relinquish_cookie(v9ses->fscache, 0); | |
158 | v9ses->fscache = NULL; | |
159 | } | |
160 | ||
161 | ||
162 | static uint16_t v9fs_cache_inode_get_key(const void *cookie_netfs_data, | |
163 | void *buffer, uint16_t bufmax) | |
164 | { | |
165 | const struct v9fs_cookie *vcookie = cookie_netfs_data; | |
166 | memcpy(buffer, &vcookie->qid->path, sizeof(vcookie->qid->path)); | |
167 | ||
168 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p get key %llu", &vcookie->inode, | |
169 | vcookie->qid->path); | |
170 | return sizeof(vcookie->qid->path); | |
171 | } | |
172 | ||
173 | static void v9fs_cache_inode_get_attr(const void *cookie_netfs_data, | |
174 | uint64_t *size) | |
175 | { | |
176 | const struct v9fs_cookie *vcookie = cookie_netfs_data; | |
177 | *size = i_size_read(&vcookie->inode); | |
178 | ||
179 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p get attr %llu", &vcookie->inode, | |
180 | *size); | |
181 | } | |
182 | ||
183 | static uint16_t v9fs_cache_inode_get_aux(const void *cookie_netfs_data, | |
184 | void *buffer, uint16_t buflen) | |
185 | { | |
186 | const struct v9fs_cookie *vcookie = cookie_netfs_data; | |
187 | memcpy(buffer, &vcookie->qid->version, sizeof(vcookie->qid->version)); | |
188 | ||
189 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p get aux %u", &vcookie->inode, | |
190 | vcookie->qid->version); | |
191 | return sizeof(vcookie->qid->version); | |
192 | } | |
193 | ||
194 | static enum | |
195 | fscache_checkaux v9fs_cache_inode_check_aux(void *cookie_netfs_data, | |
196 | const void *buffer, | |
197 | uint16_t buflen) | |
198 | { | |
199 | const struct v9fs_cookie *vcookie = cookie_netfs_data; | |
200 | ||
201 | if (buflen != sizeof(vcookie->qid->version)) | |
202 | return FSCACHE_CHECKAUX_OBSOLETE; | |
203 | ||
204 | if (memcmp(buffer, &vcookie->qid->version, | |
205 | sizeof(vcookie->qid->version))) | |
206 | return FSCACHE_CHECKAUX_OBSOLETE; | |
207 | ||
208 | return FSCACHE_CHECKAUX_OKAY; | |
209 | } | |
210 | ||
211 | static void v9fs_cache_inode_now_uncached(void *cookie_netfs_data) | |
212 | { | |
213 | struct v9fs_cookie *vcookie = cookie_netfs_data; | |
214 | struct pagevec pvec; | |
215 | pgoff_t first; | |
216 | int loop, nr_pages; | |
217 | ||
218 | pagevec_init(&pvec, 0); | |
219 | first = 0; | |
220 | ||
221 | for (;;) { | |
222 | nr_pages = pagevec_lookup(&pvec, vcookie->inode.i_mapping, | |
223 | first, | |
224 | PAGEVEC_SIZE - pagevec_count(&pvec)); | |
225 | if (!nr_pages) | |
226 | break; | |
227 | ||
228 | for (loop = 0; loop < nr_pages; loop++) | |
229 | ClearPageFsCache(pvec.pages[loop]); | |
230 | ||
231 | first = pvec.pages[nr_pages - 1]->index + 1; | |
232 | ||
233 | pvec.nr = nr_pages; | |
234 | pagevec_release(&pvec); | |
235 | cond_resched(); | |
236 | } | |
237 | } | |
238 | ||
239 | const struct fscache_cookie_def v9fs_cache_inode_index_def = { | |
240 | .name = "9p.inode", | |
241 | .type = FSCACHE_COOKIE_TYPE_DATAFILE, | |
242 | .get_key = v9fs_cache_inode_get_key, | |
243 | .get_attr = v9fs_cache_inode_get_attr, | |
244 | .get_aux = v9fs_cache_inode_get_aux, | |
245 | .check_aux = v9fs_cache_inode_check_aux, | |
246 | .now_uncached = v9fs_cache_inode_now_uncached, | |
247 | }; | |
248 | ||
249 | void v9fs_cache_inode_get_cookie(struct inode *inode) | |
250 | { | |
251 | struct v9fs_cookie *vcookie; | |
252 | struct v9fs_session_info *v9ses; | |
253 | ||
254 | if (!S_ISREG(inode->i_mode)) | |
255 | return; | |
256 | ||
257 | vcookie = v9fs_inode2cookie(inode); | |
258 | if (vcookie->fscache) | |
259 | return; | |
260 | ||
261 | v9ses = v9fs_inode2v9ses(inode); | |
262 | vcookie->fscache = fscache_acquire_cookie(v9ses->fscache, | |
263 | &v9fs_cache_inode_index_def, | |
264 | vcookie); | |
265 | ||
266 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p get cookie %p", inode, | |
267 | vcookie->fscache); | |
268 | } | |
269 | ||
270 | void v9fs_cache_inode_put_cookie(struct inode *inode) | |
271 | { | |
272 | struct v9fs_cookie *vcookie = v9fs_inode2cookie(inode); | |
273 | ||
274 | if (!vcookie->fscache) | |
275 | return; | |
276 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p put cookie %p", inode, | |
277 | vcookie->fscache); | |
278 | ||
279 | fscache_relinquish_cookie(vcookie->fscache, 0); | |
280 | vcookie->fscache = NULL; | |
281 | } | |
282 | ||
283 | void v9fs_cache_inode_flush_cookie(struct inode *inode) | |
284 | { | |
285 | struct v9fs_cookie *vcookie = v9fs_inode2cookie(inode); | |
286 | ||
287 | if (!vcookie->fscache) | |
288 | return; | |
289 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p flush cookie %p", inode, | |
290 | vcookie->fscache); | |
291 | ||
292 | fscache_relinquish_cookie(vcookie->fscache, 1); | |
293 | vcookie->fscache = NULL; | |
294 | } | |
295 | ||
296 | void v9fs_cache_inode_set_cookie(struct inode *inode, struct file *filp) | |
297 | { | |
298 | struct v9fs_cookie *vcookie = v9fs_inode2cookie(inode); | |
299 | struct p9_fid *fid; | |
300 | ||
301 | if (!vcookie->fscache) | |
302 | return; | |
303 | ||
304 | spin_lock(&vcookie->lock); | |
305 | fid = filp->private_data; | |
306 | if ((filp->f_flags & O_ACCMODE) != O_RDONLY) | |
307 | v9fs_cache_inode_flush_cookie(inode); | |
308 | else | |
309 | v9fs_cache_inode_get_cookie(inode); | |
310 | ||
311 | spin_unlock(&vcookie->lock); | |
312 | } | |
313 | ||
314 | void v9fs_cache_inode_reset_cookie(struct inode *inode) | |
315 | { | |
316 | struct v9fs_cookie *vcookie = v9fs_inode2cookie(inode); | |
317 | struct v9fs_session_info *v9ses; | |
318 | struct fscache_cookie *old; | |
319 | ||
320 | if (!vcookie->fscache) | |
321 | return; | |
322 | ||
323 | old = vcookie->fscache; | |
324 | ||
325 | spin_lock(&vcookie->lock); | |
326 | fscache_relinquish_cookie(vcookie->fscache, 1); | |
327 | ||
328 | v9ses = v9fs_inode2v9ses(inode); | |
329 | vcookie->fscache = fscache_acquire_cookie(v9ses->fscache, | |
330 | &v9fs_cache_inode_index_def, | |
331 | vcookie); | |
332 | ||
333 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p revalidating cookie old %p new %p", | |
334 | inode, old, vcookie->fscache); | |
335 | ||
336 | spin_unlock(&vcookie->lock); | |
337 | } | |
338 | ||
339 | int __v9fs_fscache_release_page(struct page *page, gfp_t gfp) | |
340 | { | |
341 | struct inode *inode = page->mapping->host; | |
342 | struct v9fs_cookie *vcookie = v9fs_inode2cookie(inode); | |
343 | ||
344 | BUG_ON(!vcookie->fscache); | |
345 | ||
6f054164 | 346 | return fscache_maybe_release_page(vcookie->fscache, page, gfp); |
60e78d2c AK |
347 | } |
348 | ||
349 | void __v9fs_fscache_invalidate_page(struct page *page) | |
350 | { | |
351 | struct inode *inode = page->mapping->host; | |
352 | struct v9fs_cookie *vcookie = v9fs_inode2cookie(inode); | |
353 | ||
354 | BUG_ON(!vcookie->fscache); | |
355 | ||
356 | if (PageFsCache(page)) { | |
357 | fscache_wait_on_page_write(vcookie->fscache, page); | |
358 | BUG_ON(!PageLocked(page)); | |
359 | fscache_uncache_page(vcookie->fscache, page); | |
60e78d2c AK |
360 | } |
361 | } | |
362 | ||
363 | static void v9fs_vfs_readpage_complete(struct page *page, void *data, | |
364 | int error) | |
365 | { | |
366 | if (!error) | |
367 | SetPageUptodate(page); | |
368 | ||
369 | unlock_page(page); | |
370 | } | |
371 | ||
372 | /** | |
373 | * __v9fs_readpage_from_fscache - read a page from cache | |
374 | * | |
375 | * Returns 0 if the pages are in cache and a BIO is submitted, | |
376 | * 1 if the pages are not in cache and -error otherwise. | |
377 | */ | |
378 | ||
379 | int __v9fs_readpage_from_fscache(struct inode *inode, struct page *page) | |
380 | { | |
381 | int ret; | |
382 | const struct v9fs_cookie *vcookie = v9fs_inode2cookie(inode); | |
383 | ||
384 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p page %p", inode, page); | |
385 | if (!vcookie->fscache) | |
386 | return -ENOBUFS; | |
387 | ||
388 | ret = fscache_read_or_alloc_page(vcookie->fscache, | |
389 | page, | |
390 | v9fs_vfs_readpage_complete, | |
391 | NULL, | |
392 | GFP_KERNEL); | |
393 | switch (ret) { | |
394 | case -ENOBUFS: | |
395 | case -ENODATA: | |
396 | P9_DPRINTK(P9_DEBUG_FSC, "page/inode not in cache %d", ret); | |
397 | return 1; | |
398 | case 0: | |
399 | P9_DPRINTK(P9_DEBUG_FSC, "BIO submitted"); | |
400 | return ret; | |
401 | default: | |
402 | P9_DPRINTK(P9_DEBUG_FSC, "ret %d", ret); | |
403 | return ret; | |
404 | } | |
405 | } | |
406 | ||
407 | /** | |
408 | * __v9fs_readpages_from_fscache - read multiple pages from cache | |
409 | * | |
410 | * Returns 0 if the pages are in cache and a BIO is submitted, | |
411 | * 1 if the pages are not in cache and -error otherwise. | |
412 | */ | |
413 | ||
414 | int __v9fs_readpages_from_fscache(struct inode *inode, | |
415 | struct address_space *mapping, | |
416 | struct list_head *pages, | |
417 | unsigned *nr_pages) | |
418 | { | |
419 | int ret; | |
420 | const struct v9fs_cookie *vcookie = v9fs_inode2cookie(inode); | |
421 | ||
422 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p pages %u", inode, *nr_pages); | |
423 | if (!vcookie->fscache) | |
424 | return -ENOBUFS; | |
425 | ||
426 | ret = fscache_read_or_alloc_pages(vcookie->fscache, | |
427 | mapping, pages, nr_pages, | |
428 | v9fs_vfs_readpage_complete, | |
429 | NULL, | |
430 | mapping_gfp_mask(mapping)); | |
431 | switch (ret) { | |
432 | case -ENOBUFS: | |
433 | case -ENODATA: | |
434 | P9_DPRINTK(P9_DEBUG_FSC, "pages/inodes not in cache %d", ret); | |
435 | return 1; | |
436 | case 0: | |
437 | BUG_ON(!list_empty(pages)); | |
438 | BUG_ON(*nr_pages != 0); | |
439 | P9_DPRINTK(P9_DEBUG_FSC, "BIO submitted"); | |
440 | return ret; | |
441 | default: | |
442 | P9_DPRINTK(P9_DEBUG_FSC, "ret %d", ret); | |
443 | return ret; | |
444 | } | |
445 | } | |
446 | ||
447 | /** | |
448 | * __v9fs_readpage_to_fscache - write a page to the cache | |
449 | * | |
450 | */ | |
451 | ||
452 | void __v9fs_readpage_to_fscache(struct inode *inode, struct page *page) | |
453 | { | |
454 | int ret; | |
455 | const struct v9fs_cookie *vcookie = v9fs_inode2cookie(inode); | |
456 | ||
457 | P9_DPRINTK(P9_DEBUG_FSC, "inode %p page %p", inode, page); | |
458 | ret = fscache_write_page(vcookie->fscache, page, GFP_KERNEL); | |
459 | P9_DPRINTK(P9_DEBUG_FSC, "ret = %d", ret); | |
460 | if (ret != 0) | |
461 | v9fs_uncache_page(inode, page); | |
462 | } |