450 lines
		
	
	
	
		
			12 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			450 lines
		
	
	
	
		
			12 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /* 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, | ||
|  | }; |