diff options
Diffstat (limited to 'fs/cachefiles/interface.c')
| -rw-r--r-- | fs/cachefiles/interface.c | 449 | 
1 files changed, 449 insertions, 0 deletions
diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c new file mode 100644 index 00000000000..1e962348d11 --- /dev/null +++ b/fs/cachefiles/interface.c @@ -0,0 +1,449 @@ +/* FS-Cache interface to CacheFiles + * + * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/mount.h> +#include <linux/buffer_head.h> +#include "internal.h" + +#define list_to_page(head) (list_entry((head)->prev, struct page, lru)) + +struct cachefiles_lookup_data { +	struct cachefiles_xattr	*auxdata;	/* auxiliary data */ +	char			*key;		/* key path */ +}; + +static int cachefiles_attr_changed(struct fscache_object *_object); + +/* + * allocate an object record for a cookie lookup and prepare the lookup data + */ +static struct fscache_object *cachefiles_alloc_object( +	struct fscache_cache *_cache, +	struct fscache_cookie *cookie) +{ +	struct cachefiles_lookup_data *lookup_data; +	struct cachefiles_object *object; +	struct cachefiles_cache *cache; +	struct cachefiles_xattr *auxdata; +	unsigned keylen, auxlen; +	void *buffer; +	char *key; + +	cache = container_of(_cache, struct cachefiles_cache, cache); + +	_enter("{%s},%p,", cache->cache.identifier, cookie); + +	lookup_data = kmalloc(sizeof(*lookup_data), GFP_KERNEL); +	if (!lookup_data) +		goto nomem_lookup_data; + +	/* create a new object record and a temporary leaf image */ +	object = kmem_cache_alloc(cachefiles_object_jar, GFP_KERNEL); +	if (!object) +		goto nomem_object; + +	ASSERTCMP(object->backer, ==, NULL); + +	BUG_ON(test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)); +	atomic_set(&object->usage, 1); + +	fscache_object_init(&object->fscache, cookie, &cache->cache); + +	object->type = cookie->def->type; + +	/* get hold of the raw key +	 * - stick the length on the front and leave space on the back for the +	 *   encoder +	 */ +	buffer = kmalloc((2 + 512) + 3, GFP_KERNEL); +	if (!buffer) +		goto nomem_buffer; + +	keylen = cookie->def->get_key(cookie->netfs_data, buffer + 2, 512); +	ASSERTCMP(keylen, <, 512); + +	*(uint16_t *)buffer = keylen; +	((char *)buffer)[keylen + 2] = 0; +	((char *)buffer)[keylen + 3] = 0; +	((char *)buffer)[keylen + 4] = 0; + +	/* turn the raw key into something that can work with as a filename */ +	key = cachefiles_cook_key(buffer, keylen + 2, object->type); +	if (!key) +		goto nomem_key; + +	/* get hold of the auxiliary data and prepend the object type */ +	auxdata = buffer; +	auxlen = 0; +	if (cookie->def->get_aux) { +		auxlen = cookie->def->get_aux(cookie->netfs_data, +					      auxdata->data, 511); +		ASSERTCMP(auxlen, <, 511); +	} + +	auxdata->len = auxlen + 1; +	auxdata->type = cookie->def->type; + +	lookup_data->auxdata = auxdata; +	lookup_data->key = key; +	object->lookup_data = lookup_data; + +	_leave(" = %p [%p]", &object->fscache, lookup_data); +	return &object->fscache; + +nomem_key: +	kfree(buffer); +nomem_buffer: +	BUG_ON(test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)); +	kmem_cache_free(cachefiles_object_jar, object); +	fscache_object_destroyed(&cache->cache); +nomem_object: +	kfree(lookup_data); +nomem_lookup_data: +	_leave(" = -ENOMEM"); +	return ERR_PTR(-ENOMEM); +} + +/* + * attempt to look up the nominated node in this cache + */ +static void cachefiles_lookup_object(struct fscache_object *_object) +{ +	struct cachefiles_lookup_data *lookup_data; +	struct cachefiles_object *parent, *object; +	struct cachefiles_cache *cache; +	const struct cred *saved_cred; +	int ret; + +	_enter("{OBJ%x}", _object->debug_id); + +	cache = container_of(_object->cache, struct cachefiles_cache, cache); +	parent = container_of(_object->parent, +			      struct cachefiles_object, fscache); +	object = container_of(_object, struct cachefiles_object, fscache); +	lookup_data = object->lookup_data; + +	ASSERTCMP(lookup_data, !=, NULL); + +	/* look up the key, creating any missing bits */ +	cachefiles_begin_secure(cache, &saved_cred); +	ret = cachefiles_walk_to_object(parent, object, +					lookup_data->key, +					lookup_data->auxdata); +	cachefiles_end_secure(cache, saved_cred); + +	/* polish off by setting the attributes of non-index files */ +	if (ret == 0 && +	    object->fscache.cookie->def->type != FSCACHE_COOKIE_TYPE_INDEX) +		cachefiles_attr_changed(&object->fscache); + +	if (ret < 0) { +		printk(KERN_WARNING "CacheFiles: Lookup failed error %d\n", +		       ret); +		fscache_object_lookup_error(&object->fscache); +	} + +	_leave(" [%d]", ret); +} + +/* + * indication of lookup completion + */ +static void cachefiles_lookup_complete(struct fscache_object *_object) +{ +	struct cachefiles_object *object; + +	object = container_of(_object, struct cachefiles_object, fscache); + +	_enter("{OBJ%x,%p}", object->fscache.debug_id, object->lookup_data); + +	if (object->lookup_data) { +		kfree(object->lookup_data->key); +		kfree(object->lookup_data->auxdata); +		kfree(object->lookup_data); +		object->lookup_data = NULL; +	} +} + +/* + * increment the usage count on an inode object (may fail if unmounting) + */ +static +struct fscache_object *cachefiles_grab_object(struct fscache_object *_object) +{ +	struct cachefiles_object *object = +		container_of(_object, struct cachefiles_object, fscache); + +	_enter("{OBJ%x,%d}", _object->debug_id, atomic_read(&object->usage)); + +#ifdef CACHEFILES_DEBUG_SLAB +	ASSERT((atomic_read(&object->usage) & 0xffff0000) != 0x6b6b0000); +#endif + +	atomic_inc(&object->usage); +	return &object->fscache; +} + +/* + * update the auxilliary data for an object object on disk + */ +static void cachefiles_update_object(struct fscache_object *_object) +{ +	struct cachefiles_object *object; +	struct cachefiles_xattr *auxdata; +	struct cachefiles_cache *cache; +	struct fscache_cookie *cookie; +	const struct cred *saved_cred; +	unsigned auxlen; + +	_enter("{OBJ%x}", _object->debug_id); + +	object = container_of(_object, struct cachefiles_object, fscache); +	cache = container_of(object->fscache.cache, struct cachefiles_cache, +			     cache); +	cookie = object->fscache.cookie; + +	if (!cookie->def->get_aux) { +		_leave(" [no aux]"); +		return; +	} + +	auxdata = kmalloc(2 + 512 + 3, GFP_KERNEL); +	if (!auxdata) { +		_leave(" [nomem]"); +		return; +	} + +	auxlen = cookie->def->get_aux(cookie->netfs_data, auxdata->data, 511); +	ASSERTCMP(auxlen, <, 511); + +	auxdata->len = auxlen + 1; +	auxdata->type = cookie->def->type; + +	cachefiles_begin_secure(cache, &saved_cred); +	cachefiles_update_object_xattr(object, auxdata); +	cachefiles_end_secure(cache, saved_cred); +	kfree(auxdata); +	_leave(""); +} + +/* + * discard the resources pinned by an object and effect retirement if + * requested + */ +static void cachefiles_drop_object(struct fscache_object *_object) +{ +	struct cachefiles_object *object; +	struct cachefiles_cache *cache; +	const struct cred *saved_cred; + +	ASSERT(_object); + +	object = container_of(_object, struct cachefiles_object, fscache); + +	_enter("{OBJ%x,%d}", +	       object->fscache.debug_id, atomic_read(&object->usage)); + +	cache = container_of(object->fscache.cache, +			     struct cachefiles_cache, cache); + +#ifdef CACHEFILES_DEBUG_SLAB +	ASSERT((atomic_read(&object->usage) & 0xffff0000) != 0x6b6b0000); +#endif + +	/* delete retired objects */ +	if (object->fscache.state == FSCACHE_OBJECT_RECYCLING && +	    _object != cache->cache.fsdef +	    ) { +		_debug("- retire object OBJ%x", object->fscache.debug_id); +		cachefiles_begin_secure(cache, &saved_cred); +		cachefiles_delete_object(cache, object); +		cachefiles_end_secure(cache, saved_cred); +	} + +	/* close the filesystem stuff attached to the object */ +	if (object->backer != object->dentry) +		dput(object->backer); +	object->backer = NULL; + +	/* note that the object is now inactive */ +	if (test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)) { +		write_lock(&cache->active_lock); +		if (!test_and_clear_bit(CACHEFILES_OBJECT_ACTIVE, +					&object->flags)) +			BUG(); +		rb_erase(&object->active_node, &cache->active_nodes); +		wake_up_bit(&object->flags, CACHEFILES_OBJECT_ACTIVE); +		write_unlock(&cache->active_lock); +	} + +	dput(object->dentry); +	object->dentry = NULL; + +	_leave(""); +} + +/* + * dispose of a reference to an object + */ +static void cachefiles_put_object(struct fscache_object *_object) +{ +	struct cachefiles_object *object; +	struct fscache_cache *cache; + +	ASSERT(_object); + +	object = container_of(_object, struct cachefiles_object, fscache); + +	_enter("{OBJ%x,%d}", +	       object->fscache.debug_id, atomic_read(&object->usage)); + +#ifdef CACHEFILES_DEBUG_SLAB +	ASSERT((atomic_read(&object->usage) & 0xffff0000) != 0x6b6b0000); +#endif + +	ASSERTIFCMP(object->fscache.parent, +		    object->fscache.parent->n_children, >, 0); + +	if (atomic_dec_and_test(&object->usage)) { +		_debug("- kill object OBJ%x", object->fscache.debug_id); + +		ASSERT(!test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)); +		ASSERTCMP(object->fscache.parent, ==, NULL); +		ASSERTCMP(object->backer, ==, NULL); +		ASSERTCMP(object->dentry, ==, NULL); +		ASSERTCMP(object->fscache.n_ops, ==, 0); +		ASSERTCMP(object->fscache.n_children, ==, 0); + +		if (object->lookup_data) { +			kfree(object->lookup_data->key); +			kfree(object->lookup_data->auxdata); +			kfree(object->lookup_data); +			object->lookup_data = NULL; +		} + +		cache = object->fscache.cache; +		kmem_cache_free(cachefiles_object_jar, object); +		fscache_object_destroyed(cache); +	} + +	_leave(""); +} + +/* + * sync a cache + */ +static void cachefiles_sync_cache(struct fscache_cache *_cache) +{ +	struct cachefiles_cache *cache; +	const struct cred *saved_cred; +	int ret; + +	_enter("%p", _cache); + +	cache = container_of(_cache, struct cachefiles_cache, cache); + +	/* make sure all pages pinned by operations on behalf of the netfs are +	 * written to disc */ +	cachefiles_begin_secure(cache, &saved_cred); +	ret = fsync_super(cache->mnt->mnt_sb); +	cachefiles_end_secure(cache, saved_cred); + +	if (ret == -EIO) +		cachefiles_io_error(cache, +				    "Attempt to sync backing fs superblock" +				    " returned error %d", +				    ret); +} + +/* + * notification the attributes on an object have changed + * - called with reads/writes excluded by FS-Cache + */ +static int cachefiles_attr_changed(struct fscache_object *_object) +{ +	struct cachefiles_object *object; +	struct cachefiles_cache *cache; +	const struct cred *saved_cred; +	struct iattr newattrs; +	uint64_t ni_size; +	loff_t oi_size; +	int ret; + +	_object->cookie->def->get_attr(_object->cookie->netfs_data, &ni_size); + +	_enter("{OBJ%x},[%llu]", +	       _object->debug_id, (unsigned long long) ni_size); + +	object = container_of(_object, struct cachefiles_object, fscache); +	cache = container_of(object->fscache.cache, +			     struct cachefiles_cache, cache); + +	if (ni_size == object->i_size) +		return 0; + +	if (!object->backer) +		return -ENOBUFS; + +	ASSERT(S_ISREG(object->backer->d_inode->i_mode)); + +	fscache_set_store_limit(&object->fscache, ni_size); + +	oi_size = i_size_read(object->backer->d_inode); +	if (oi_size == ni_size) +		return 0; + +	newattrs.ia_size = ni_size; +	newattrs.ia_valid = ATTR_SIZE; + +	cachefiles_begin_secure(cache, &saved_cred); +	mutex_lock(&object->backer->d_inode->i_mutex); +	ret = notify_change(object->backer, &newattrs); +	mutex_unlock(&object->backer->d_inode->i_mutex); +	cachefiles_end_secure(cache, saved_cred); + +	if (ret == -EIO) { +		fscache_set_store_limit(&object->fscache, 0); +		cachefiles_io_error_obj(object, "Size set failed"); +		ret = -ENOBUFS; +	} + +	_leave(" = %d", ret); +	return ret; +} + +/* + * dissociate a cache from all the pages it was backing + */ +static void cachefiles_dissociate_pages(struct fscache_cache *cache) +{ +	_enter(""); +} + +const struct fscache_cache_ops cachefiles_cache_ops = { +	.name			= "cachefiles", +	.alloc_object		= cachefiles_alloc_object, +	.lookup_object		= cachefiles_lookup_object, +	.lookup_complete	= cachefiles_lookup_complete, +	.grab_object		= cachefiles_grab_object, +	.update_object		= cachefiles_update_object, +	.drop_object		= cachefiles_drop_object, +	.put_object		= cachefiles_put_object, +	.sync_cache		= cachefiles_sync_cache, +	.attr_changed		= cachefiles_attr_changed, +	.read_or_alloc_page	= cachefiles_read_or_alloc_page, +	.read_or_alloc_pages	= cachefiles_read_or_alloc_pages, +	.allocate_page		= cachefiles_allocate_page, +	.allocate_pages		= cachefiles_allocate_pages, +	.write_page		= cachefiles_write_page, +	.uncache_page		= cachefiles_uncache_page, +	.dissociate_pages	= cachefiles_dissociate_pages, +};  |