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