| David Howells | 0e04d4c | 2009-04-03 16:42:37 +0100 | [diff] [blame] | 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); | 
| David Howells | 4c515dd | 2009-04-03 16:42:37 +0100 | [diff] [blame] | 19 | DECLARE_WAIT_QUEUE_HEAD(fscache_cache_cleared_wq); | 
 | 20 | EXPORT_SYMBOL(fscache_cache_cleared_wq); | 
| David Howells | 0e04d4c | 2009-04-03 16:42:37 +0100 | [diff] [blame] | 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 | } | 
| David Howells | 4c515dd | 2009-04-03 16:42:37 +0100 | [diff] [blame] | 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 |  | 
| David Howells | 952efe7 | 2009-04-03 16:42:39 +0100 | [diff] [blame] | 197 | 	INIT_WORK(&cache->op_gc, fscache_operation_gc); | 
| David Howells | 4c515dd | 2009-04-03 16:42:37 +0100 | [diff] [blame] | 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); |