diff options
Diffstat (limited to 'fs/fscache/cache.c')
-rw-r--r-- | fs/fscache/cache.c | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/fs/fscache/cache.c b/fs/fscache/cache.c new file mode 100644 index 000000000000..e21985bbb1fb --- /dev/null +++ b/fs/fscache/cache.c | |||
@@ -0,0 +1,415 @@ | |||
1 | /* FS-Cache cache handling | ||
2 | * | ||
3 | * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. | ||
4 | * Written by David Howells (dhowells@redhat.com) | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License | ||
8 | * as published by the Free Software Foundation; either version | ||
9 | * 2 of the License, or (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #define FSCACHE_DEBUG_LEVEL CACHE | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/slab.h> | ||
15 | #include "internal.h" | ||
16 | |||
17 | LIST_HEAD(fscache_cache_list); | ||
18 | DECLARE_RWSEM(fscache_addremove_sem); | ||
19 | DECLARE_WAIT_QUEUE_HEAD(fscache_cache_cleared_wq); | ||
20 | EXPORT_SYMBOL(fscache_cache_cleared_wq); | ||
21 | |||
22 | static LIST_HEAD(fscache_cache_tag_list); | ||
23 | |||
24 | /* | ||
25 | * look up a cache tag | ||
26 | */ | ||
27 | struct fscache_cache_tag *__fscache_lookup_cache_tag(const char *name) | ||
28 | { | ||
29 | struct fscache_cache_tag *tag, *xtag; | ||
30 | |||
31 | /* firstly check for the existence of the tag under read lock */ | ||
32 | down_read(&fscache_addremove_sem); | ||
33 | |||
34 | list_for_each_entry(tag, &fscache_cache_tag_list, link) { | ||
35 | if (strcmp(tag->name, name) == 0) { | ||
36 | atomic_inc(&tag->usage); | ||
37 | up_read(&fscache_addremove_sem); | ||
38 | return tag; | ||
39 | } | ||
40 | } | ||
41 | |||
42 | up_read(&fscache_addremove_sem); | ||
43 | |||
44 | /* the tag does not exist - create a candidate */ | ||
45 | xtag = kzalloc(sizeof(*xtag) + strlen(name) + 1, GFP_KERNEL); | ||
46 | if (!xtag) | ||
47 | /* return a dummy tag if out of memory */ | ||
48 | return ERR_PTR(-ENOMEM); | ||
49 | |||
50 | atomic_set(&xtag->usage, 1); | ||
51 | strcpy(xtag->name, name); | ||
52 | |||
53 | /* write lock, search again and add if still not present */ | ||
54 | down_write(&fscache_addremove_sem); | ||
55 | |||
56 | list_for_each_entry(tag, &fscache_cache_tag_list, link) { | ||
57 | if (strcmp(tag->name, name) == 0) { | ||
58 | atomic_inc(&tag->usage); | ||
59 | up_write(&fscache_addremove_sem); | ||
60 | kfree(xtag); | ||
61 | return tag; | ||
62 | } | ||
63 | } | ||
64 | |||
65 | list_add_tail(&xtag->link, &fscache_cache_tag_list); | ||
66 | up_write(&fscache_addremove_sem); | ||
67 | return xtag; | ||
68 | } | ||
69 | |||
70 | /* | ||
71 | * release a reference to a cache tag | ||
72 | */ | ||
73 | void __fscache_release_cache_tag(struct fscache_cache_tag *tag) | ||
74 | { | ||
75 | if (tag != ERR_PTR(-ENOMEM)) { | ||
76 | down_write(&fscache_addremove_sem); | ||
77 | |||
78 | if (atomic_dec_and_test(&tag->usage)) | ||
79 | list_del_init(&tag->link); | ||
80 | else | ||
81 | tag = NULL; | ||
82 | |||
83 | up_write(&fscache_addremove_sem); | ||
84 | |||
85 | kfree(tag); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | /* | ||
90 | * select a cache in which to store an object | ||
91 | * - the cache addremove semaphore must be at least read-locked by the caller | ||
92 | * - the object will never be an index | ||
93 | */ | ||
94 | struct fscache_cache *fscache_select_cache_for_object( | ||
95 | struct fscache_cookie *cookie) | ||
96 | { | ||
97 | struct fscache_cache_tag *tag; | ||
98 | struct fscache_object *object; | ||
99 | struct fscache_cache *cache; | ||
100 | |||
101 | _enter(""); | ||
102 | |||
103 | if (list_empty(&fscache_cache_list)) { | ||
104 | _leave(" = NULL [no cache]"); | ||
105 | return NULL; | ||
106 | } | ||
107 | |||
108 | /* we check the parent to determine the cache to use */ | ||
109 | spin_lock(&cookie->lock); | ||
110 | |||
111 | /* the first in the parent's backing list should be the preferred | ||
112 | * cache */ | ||
113 | if (!hlist_empty(&cookie->backing_objects)) { | ||
114 | object = hlist_entry(cookie->backing_objects.first, | ||
115 | struct fscache_object, cookie_link); | ||
116 | |||
117 | cache = object->cache; | ||
118 | if (object->state >= FSCACHE_OBJECT_DYING || | ||
119 | test_bit(FSCACHE_IOERROR, &cache->flags)) | ||
120 | cache = NULL; | ||
121 | |||
122 | spin_unlock(&cookie->lock); | ||
123 | _leave(" = %p [parent]", cache); | ||
124 | return cache; | ||
125 | } | ||
126 | |||
127 | /* the parent is unbacked */ | ||
128 | if (cookie->def->type != FSCACHE_COOKIE_TYPE_INDEX) { | ||
129 | /* cookie not an index and is unbacked */ | ||
130 | spin_unlock(&cookie->lock); | ||
131 | _leave(" = NULL [cookie ub,ni]"); | ||
132 | return NULL; | ||
133 | } | ||
134 | |||
135 | spin_unlock(&cookie->lock); | ||
136 | |||
137 | if (!cookie->def->select_cache) | ||
138 | goto no_preference; | ||
139 | |||
140 | /* ask the netfs for its preference */ | ||
141 | tag = cookie->def->select_cache(cookie->parent->netfs_data, | ||
142 | cookie->netfs_data); | ||
143 | if (!tag) | ||
144 | goto no_preference; | ||
145 | |||
146 | if (tag == ERR_PTR(-ENOMEM)) { | ||
147 | _leave(" = NULL [nomem tag]"); | ||
148 | return NULL; | ||
149 | } | ||
150 | |||
151 | if (!tag->cache) { | ||
152 | _leave(" = NULL [unbacked tag]"); | ||
153 | return NULL; | ||
154 | } | ||
155 | |||
156 | if (test_bit(FSCACHE_IOERROR, &tag->cache->flags)) | ||
157 | return NULL; | ||
158 | |||
159 | _leave(" = %p [specific]", tag->cache); | ||
160 | return tag->cache; | ||
161 | |||
162 | no_preference: | ||
163 | /* netfs has no preference - just select first cache */ | ||
164 | cache = list_entry(fscache_cache_list.next, | ||
165 | struct fscache_cache, link); | ||
166 | _leave(" = %p [first]", cache); | ||
167 | return cache; | ||
168 | } | ||
169 | |||
170 | /** | ||
171 | * fscache_init_cache - Initialise a cache record | ||
172 | * @cache: The cache record to be initialised | ||
173 | * @ops: The cache operations to be installed in that record | ||
174 | * @idfmt: Format string to define identifier | ||
175 | * @...: sprintf-style arguments | ||
176 | * | ||
177 | * Initialise a record of a cache and fill in the name. | ||
178 | * | ||
179 | * See Documentation/filesystems/caching/backend-api.txt for a complete | ||
180 | * description. | ||
181 | */ | ||
182 | void fscache_init_cache(struct fscache_cache *cache, | ||
183 | const struct fscache_cache_ops *ops, | ||
184 | const char *idfmt, | ||
185 | ...) | ||
186 | { | ||
187 | va_list va; | ||
188 | |||
189 | memset(cache, 0, sizeof(*cache)); | ||
190 | |||
191 | cache->ops = ops; | ||
192 | |||
193 | va_start(va, idfmt); | ||
194 | vsnprintf(cache->identifier, sizeof(cache->identifier), idfmt, va); | ||
195 | va_end(va); | ||
196 | |||
197 | INIT_WORK(&cache->op_gc, fscache_operation_gc); | ||
198 | INIT_LIST_HEAD(&cache->link); | ||
199 | INIT_LIST_HEAD(&cache->object_list); | ||
200 | INIT_LIST_HEAD(&cache->op_gc_list); | ||
201 | spin_lock_init(&cache->object_list_lock); | ||
202 | spin_lock_init(&cache->op_gc_list_lock); | ||
203 | } | ||
204 | EXPORT_SYMBOL(fscache_init_cache); | ||
205 | |||
206 | /** | ||
207 | * fscache_add_cache - Declare a cache as being open for business | ||
208 | * @cache: The record describing the cache | ||
209 | * @ifsdef: The record of the cache object describing the top-level index | ||
210 | * @tagname: The tag describing this cache | ||
211 | * | ||
212 | * Add a cache to the system, making it available for netfs's to use. | ||
213 | * | ||
214 | * See Documentation/filesystems/caching/backend-api.txt for a complete | ||
215 | * description. | ||
216 | */ | ||
217 | int fscache_add_cache(struct fscache_cache *cache, | ||
218 | struct fscache_object *ifsdef, | ||
219 | const char *tagname) | ||
220 | { | ||
221 | struct fscache_cache_tag *tag; | ||
222 | |||
223 | BUG_ON(!cache->ops); | ||
224 | BUG_ON(!ifsdef); | ||
225 | |||
226 | cache->flags = 0; | ||
227 | ifsdef->event_mask = ULONG_MAX & ~(1 << FSCACHE_OBJECT_EV_CLEARED); | ||
228 | ifsdef->state = FSCACHE_OBJECT_ACTIVE; | ||
229 | |||
230 | if (!tagname) | ||
231 | tagname = cache->identifier; | ||
232 | |||
233 | BUG_ON(!tagname[0]); | ||
234 | |||
235 | _enter("{%s.%s},,%s", cache->ops->name, cache->identifier, tagname); | ||
236 | |||
237 | /* we use the cache tag to uniquely identify caches */ | ||
238 | tag = __fscache_lookup_cache_tag(tagname); | ||
239 | if (IS_ERR(tag)) | ||
240 | goto nomem; | ||
241 | |||
242 | if (test_and_set_bit(FSCACHE_TAG_RESERVED, &tag->flags)) | ||
243 | goto tag_in_use; | ||
244 | |||
245 | cache->kobj = kobject_create_and_add(tagname, fscache_root); | ||
246 | if (!cache->kobj) | ||
247 | goto error; | ||
248 | |||
249 | ifsdef->cookie = &fscache_fsdef_index; | ||
250 | ifsdef->cache = cache; | ||
251 | cache->fsdef = ifsdef; | ||
252 | |||
253 | down_write(&fscache_addremove_sem); | ||
254 | |||
255 | tag->cache = cache; | ||
256 | cache->tag = tag; | ||
257 | |||
258 | /* add the cache to the list */ | ||
259 | list_add(&cache->link, &fscache_cache_list); | ||
260 | |||
261 | /* add the cache's netfs definition index object to the cache's | ||
262 | * list */ | ||
263 | spin_lock(&cache->object_list_lock); | ||
264 | list_add_tail(&ifsdef->cache_link, &cache->object_list); | ||
265 | spin_unlock(&cache->object_list_lock); | ||
266 | |||
267 | /* add the cache's netfs definition index object to the top level index | ||
268 | * cookie as a known backing object */ | ||
269 | spin_lock(&fscache_fsdef_index.lock); | ||
270 | |||
271 | hlist_add_head(&ifsdef->cookie_link, | ||
272 | &fscache_fsdef_index.backing_objects); | ||
273 | |||
274 | atomic_inc(&fscache_fsdef_index.usage); | ||
275 | |||
276 | /* done */ | ||
277 | spin_unlock(&fscache_fsdef_index.lock); | ||
278 | up_write(&fscache_addremove_sem); | ||
279 | |||
280 | printk(KERN_NOTICE "FS-Cache: Cache \"%s\" added (type %s)\n", | ||
281 | cache->tag->name, cache->ops->name); | ||
282 | kobject_uevent(cache->kobj, KOBJ_ADD); | ||
283 | |||
284 | _leave(" = 0 [%s]", cache->identifier); | ||
285 | return 0; | ||
286 | |||
287 | tag_in_use: | ||
288 | printk(KERN_ERR "FS-Cache: Cache tag '%s' already in use\n", tagname); | ||
289 | __fscache_release_cache_tag(tag); | ||
290 | _leave(" = -EXIST"); | ||
291 | return -EEXIST; | ||
292 | |||
293 | error: | ||
294 | __fscache_release_cache_tag(tag); | ||
295 | _leave(" = -EINVAL"); | ||
296 | return -EINVAL; | ||
297 | |||
298 | nomem: | ||
299 | _leave(" = -ENOMEM"); | ||
300 | return -ENOMEM; | ||
301 | } | ||
302 | EXPORT_SYMBOL(fscache_add_cache); | ||
303 | |||
304 | /** | ||
305 | * fscache_io_error - Note a cache I/O error | ||
306 | * @cache: The record describing the cache | ||
307 | * | ||
308 | * Note that an I/O error occurred in a cache and that it should no longer be | ||
309 | * used for anything. This also reports the error into the kernel log. | ||
310 | * | ||
311 | * See Documentation/filesystems/caching/backend-api.txt for a complete | ||
312 | * description. | ||
313 | */ | ||
314 | void fscache_io_error(struct fscache_cache *cache) | ||
315 | { | ||
316 | set_bit(FSCACHE_IOERROR, &cache->flags); | ||
317 | |||
318 | printk(KERN_ERR "FS-Cache: Cache %s stopped due to I/O error\n", | ||
319 | cache->ops->name); | ||
320 | } | ||
321 | EXPORT_SYMBOL(fscache_io_error); | ||
322 | |||
323 | /* | ||
324 | * request withdrawal of all the objects in a cache | ||
325 | * - all the objects being withdrawn are moved onto the supplied list | ||
326 | */ | ||
327 | static void fscache_withdraw_all_objects(struct fscache_cache *cache, | ||
328 | struct list_head *dying_objects) | ||
329 | { | ||
330 | struct fscache_object *object; | ||
331 | |||
332 | spin_lock(&cache->object_list_lock); | ||
333 | |||
334 | while (!list_empty(&cache->object_list)) { | ||
335 | object = list_entry(cache->object_list.next, | ||
336 | struct fscache_object, cache_link); | ||
337 | list_move_tail(&object->cache_link, dying_objects); | ||
338 | |||
339 | _debug("withdraw %p", object->cookie); | ||
340 | |||
341 | spin_lock(&object->lock); | ||
342 | spin_unlock(&cache->object_list_lock); | ||
343 | fscache_raise_event(object, FSCACHE_OBJECT_EV_WITHDRAW); | ||
344 | spin_unlock(&object->lock); | ||
345 | |||
346 | cond_resched(); | ||
347 | spin_lock(&cache->object_list_lock); | ||
348 | } | ||
349 | |||
350 | spin_unlock(&cache->object_list_lock); | ||
351 | } | ||
352 | |||
353 | /** | ||
354 | * fscache_withdraw_cache - Withdraw a cache from the active service | ||
355 | * @cache: The record describing the cache | ||
356 | * | ||
357 | * Withdraw a cache from service, unbinding all its cache objects from the | ||
358 | * netfs cookies they're currently representing. | ||
359 | * | ||
360 | * See Documentation/filesystems/caching/backend-api.txt for a complete | ||
361 | * description. | ||
362 | */ | ||
363 | void fscache_withdraw_cache(struct fscache_cache *cache) | ||
364 | { | ||
365 | LIST_HEAD(dying_objects); | ||
366 | |||
367 | _enter(""); | ||
368 | |||
369 | printk(KERN_NOTICE "FS-Cache: Withdrawing cache \"%s\"\n", | ||
370 | cache->tag->name); | ||
371 | |||
372 | /* make the cache unavailable for cookie acquisition */ | ||
373 | if (test_and_set_bit(FSCACHE_CACHE_WITHDRAWN, &cache->flags)) | ||
374 | BUG(); | ||
375 | |||
376 | down_write(&fscache_addremove_sem); | ||
377 | list_del_init(&cache->link); | ||
378 | cache->tag->cache = NULL; | ||
379 | up_write(&fscache_addremove_sem); | ||
380 | |||
381 | /* make sure all pages pinned by operations on behalf of the netfs are | ||
382 | * written to disk */ | ||
383 | cache->ops->sync_cache(cache); | ||
384 | |||
385 | /* dissociate all the netfs pages backed by this cache from the block | ||
386 | * mappings in the cache */ | ||
387 | cache->ops->dissociate_pages(cache); | ||
388 | |||
389 | /* we now have to destroy all the active objects pertaining to this | ||
390 | * cache - which we do by passing them off to thread pool to be | ||
391 | * disposed of */ | ||
392 | _debug("destroy"); | ||
393 | |||
394 | fscache_withdraw_all_objects(cache, &dying_objects); | ||
395 | |||
396 | /* wait for all extant objects to finish their outstanding operations | ||
397 | * and go away */ | ||
398 | _debug("wait for finish"); | ||
399 | wait_event(fscache_cache_cleared_wq, | ||
400 | atomic_read(&cache->object_count) == 0); | ||
401 | _debug("wait for clearance"); | ||
402 | wait_event(fscache_cache_cleared_wq, | ||
403 | list_empty(&cache->object_list)); | ||
404 | _debug("cleared"); | ||
405 | ASSERT(list_empty(&dying_objects)); | ||
406 | |||
407 | kobject_put(cache->kobj); | ||
408 | |||
409 | clear_bit(FSCACHE_TAG_RESERVED, &cache->tag->flags); | ||
410 | fscache_release_cache_tag(cache->tag); | ||
411 | cache->tag = NULL; | ||
412 | |||
413 | _leave(""); | ||
414 | } | ||
415 | EXPORT_SYMBOL(fscache_withdraw_cache); | ||