mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-01 00:58:39 +02:00 
			
		
		
		
	cachefiles: Implement backing file wrangling
Implement the wrangling of backing files, including the following pieces:
 (1) Lookup and creation of a file on disk, using a tmpfile if the file
     isn't yet present.  The file is then opened, sized for DIO and the
     file handle is attached to the cachefiles_object struct.  The inode is
     marked to indicate that it's in use by a kernel service.
 (2) Invalidation of an object, creating a tmpfile and switching the file
     pointer in the cachefiles object.
 (3) Committing a file to disk, including setting the coherency xattr on it
     and, if necessary, creating a hard link to it.
     Note that this would be a good place to use Omar Sandoval's vfs_link()
     with AT_LINK_REPLACE[1] as I may have to unlink an old file before I
     can link a tmpfile into place.
 (4) Withdrawal of open objects when a cache is being withdrawn or a cookie
     is relinquished.  This involves committing or discarding the file.
Changes
=======
ver #2:
 - Fix logging of wrong error[1].
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
cc: linux-cachefs@redhat.com
Link: https://lore.kernel.org/r/20211203094950.GA2480@kili/ [1]
Link: https://lore.kernel.org/r/163819644097.215744.4505389616742411239.stgit@warthog.procyon.org.uk/ # v1
Link: https://lore.kernel.org/r/163906949512.143852.14222856795032602080.stgit@warthog.procyon.org.uk/ # v2
Link: https://lore.kernel.org/r/163967158526.1823006.17482695321424642675.stgit@warthog.procyon.org.uk/ # v3
Link: https://lore.kernel.org/r/164021557060.640689.16373541458119269871.stgit@warthog.procyon.org.uk/ # v4
			
			
This commit is contained in:
		
							parent
							
								
									07a90e9740
								
							
						
					
					
						commit
						1f08c925e7
					
				
					 5 changed files with 619 additions and 1 deletions
				
			
		|  | @ -262,6 +262,36 @@ int cachefiles_has_space(struct cachefiles_cache *cache, | |||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Mark all the objects as being out of service and queue them all for cleanup. | ||||
|  */ | ||||
| static void cachefiles_withdraw_objects(struct cachefiles_cache *cache) | ||||
| { | ||||
| 	struct cachefiles_object *object; | ||||
| 	unsigned int count = 0; | ||||
| 
 | ||||
| 	_enter(""); | ||||
| 
 | ||||
| 	spin_lock(&cache->object_list_lock); | ||||
| 
 | ||||
| 	while (!list_empty(&cache->object_list)) { | ||||
| 		object = list_first_entry(&cache->object_list, | ||||
| 					  struct cachefiles_object, cache_link); | ||||
| 		cachefiles_see_object(object, cachefiles_obj_see_withdrawal); | ||||
| 		list_del_init(&object->cache_link); | ||||
| 		fscache_withdraw_cookie(object->cookie); | ||||
| 		count++; | ||||
| 		if ((count & 63) == 0) { | ||||
| 			spin_unlock(&cache->object_list_lock); | ||||
| 			cond_resched(); | ||||
| 			spin_lock(&cache->object_list_lock); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	spin_unlock(&cache->object_list_lock); | ||||
| 	_leave(" [%u objs]", count); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Withdraw volumes. | ||||
|  */ | ||||
|  | @ -326,7 +356,7 @@ void cachefiles_withdraw_cache(struct cachefiles_cache *cache) | |||
| 	/* we now have to destroy all the active objects pertaining to this
 | ||||
| 	 * cache - which we do by passing them off to thread pool to be | ||||
| 	 * disposed of */ | ||||
| 	// PLACEHOLDER: Withdraw objects
 | ||||
| 	cachefiles_withdraw_objects(cache); | ||||
| 	fscache_wait_for_objects(fscache); | ||||
| 
 | ||||
| 	cachefiles_withdraw_volumes(cache); | ||||
|  |  | |||
|  | @ -106,6 +106,7 @@ static int cachefiles_daemon_open(struct inode *inode, struct file *file) | |||
| 	mutex_init(&cache->daemon_mutex); | ||||
| 	init_waitqueue_head(&cache->daemon_pollwq); | ||||
| 	INIT_LIST_HEAD(&cache->volumes); | ||||
| 	INIT_LIST_HEAD(&cache->object_list); | ||||
| 	spin_lock_init(&cache->object_list_lock); | ||||
| 
 | ||||
| 	/* set default caching limits
 | ||||
|  |  | |||
|  | @ -99,8 +99,268 @@ void cachefiles_put_object(struct cachefiles_object *object, | |||
| 	_leave(""); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Adjust the size of a cache file if necessary to match the DIO size.  We keep | ||||
|  * the EOF marker a multiple of DIO blocks so that we don't fall back to doing | ||||
|  * non-DIO for a partial block straddling the EOF, but we also have to be | ||||
|  * careful of someone expanding the file and accidentally accreting the | ||||
|  * padding. | ||||
|  */ | ||||
| static int cachefiles_adjust_size(struct cachefiles_object *object) | ||||
| { | ||||
| 	struct iattr newattrs; | ||||
| 	struct file *file = object->file; | ||||
| 	uint64_t ni_size; | ||||
| 	loff_t oi_size; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ni_size = object->cookie->object_size; | ||||
| 	ni_size = round_up(ni_size, CACHEFILES_DIO_BLOCK_SIZE); | ||||
| 
 | ||||
| 	_enter("{OBJ%x},[%llu]", | ||||
| 	       object->debug_id, (unsigned long long) ni_size); | ||||
| 
 | ||||
| 	if (!file) | ||||
| 		return -ENOBUFS; | ||||
| 
 | ||||
| 	oi_size = i_size_read(file_inode(file)); | ||||
| 	if (oi_size == ni_size) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	inode_lock(file_inode(file)); | ||||
| 
 | ||||
| 	/* if there's an extension to a partial page at the end of the backing
 | ||||
| 	 * file, we need to discard the partial page so that we pick up new | ||||
| 	 * data after it */ | ||||
| 	if (oi_size & ~PAGE_MASK && ni_size > oi_size) { | ||||
| 		_debug("discard tail %llx", oi_size); | ||||
| 		newattrs.ia_valid = ATTR_SIZE; | ||||
| 		newattrs.ia_size = oi_size & PAGE_MASK; | ||||
| 		ret = cachefiles_inject_remove_error(); | ||||
| 		if (ret == 0) | ||||
| 			ret = notify_change(&init_user_ns, file->f_path.dentry, | ||||
| 					    &newattrs, NULL); | ||||
| 		if (ret < 0) | ||||
| 			goto truncate_failed; | ||||
| 	} | ||||
| 
 | ||||
| 	newattrs.ia_valid = ATTR_SIZE; | ||||
| 	newattrs.ia_size = ni_size; | ||||
| 	ret = cachefiles_inject_write_error(); | ||||
| 	if (ret == 0) | ||||
| 		ret = notify_change(&init_user_ns, file->f_path.dentry, | ||||
| 				    &newattrs, NULL); | ||||
| 
 | ||||
| truncate_failed: | ||||
| 	inode_unlock(file_inode(file)); | ||||
| 
 | ||||
| 	if (ret < 0) | ||||
| 		trace_cachefiles_io_error(NULL, file_inode(file), ret, | ||||
| 					  cachefiles_trace_notify_change_error); | ||||
| 	if (ret == -EIO) { | ||||
| 		cachefiles_io_error_obj(object, "Size set failed"); | ||||
| 		ret = -ENOBUFS; | ||||
| 	} | ||||
| 
 | ||||
| 	_leave(" = %d", ret); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Attempt to look up the nominated node in this cache | ||||
|  */ | ||||
| static bool cachefiles_lookup_cookie(struct fscache_cookie *cookie) | ||||
| { | ||||
| 	struct cachefiles_object *object; | ||||
| 	struct cachefiles_cache *cache = cookie->volume->cache->cache_priv; | ||||
| 	const struct cred *saved_cred; | ||||
| 	bool success; | ||||
| 
 | ||||
| 	object = cachefiles_alloc_object(cookie); | ||||
| 	if (!object) | ||||
| 		goto fail; | ||||
| 
 | ||||
| 	_enter("{OBJ%x}", object->debug_id); | ||||
| 
 | ||||
| 	if (!cachefiles_cook_key(object)) | ||||
| 		goto fail_put; | ||||
| 
 | ||||
| 	cookie->cache_priv = object; | ||||
| 
 | ||||
| 	cachefiles_begin_secure(cache, &saved_cred); | ||||
| 
 | ||||
| 	success = cachefiles_look_up_object(object); | ||||
| 	if (!success) | ||||
| 		goto fail_withdraw; | ||||
| 
 | ||||
| 	cachefiles_see_object(object, cachefiles_obj_see_lookup_cookie); | ||||
| 
 | ||||
| 	spin_lock(&cache->object_list_lock); | ||||
| 	list_add(&object->cache_link, &cache->object_list); | ||||
| 	spin_unlock(&cache->object_list_lock); | ||||
| 	cachefiles_adjust_size(object); | ||||
| 
 | ||||
| 	cachefiles_end_secure(cache, saved_cred); | ||||
| 	_leave(" = t"); | ||||
| 	return true; | ||||
| 
 | ||||
| fail_withdraw: | ||||
| 	cachefiles_end_secure(cache, saved_cred); | ||||
| 	cachefiles_see_object(object, cachefiles_obj_see_lookup_failed); | ||||
| 	fscache_caching_failed(cookie); | ||||
| 	_debug("failed c=%08x o=%08x", cookie->debug_id, object->debug_id); | ||||
| 	/* The caller holds an access count on the cookie, so we need them to
 | ||||
| 	 * drop it before we can withdraw the object. | ||||
| 	 */ | ||||
| 	return false; | ||||
| 
 | ||||
| fail_put: | ||||
| 	cachefiles_put_object(object, cachefiles_obj_put_alloc_fail); | ||||
| fail: | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Commit changes to the object as we drop it. | ||||
|  */ | ||||
| static void cachefiles_commit_object(struct cachefiles_object *object, | ||||
| 				     struct cachefiles_cache *cache) | ||||
| { | ||||
| 	bool update = false; | ||||
| 
 | ||||
| 	if (test_and_clear_bit(FSCACHE_COOKIE_LOCAL_WRITE, &object->cookie->flags)) | ||||
| 		update = true; | ||||
| 	if (test_and_clear_bit(FSCACHE_COOKIE_NEEDS_UPDATE, &object->cookie->flags)) | ||||
| 		update = true; | ||||
| 	if (update) | ||||
| 		cachefiles_set_object_xattr(object); | ||||
| 
 | ||||
| 	if (test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags)) | ||||
| 		cachefiles_commit_tmpfile(cache, object); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Finalise and object and close the VFS structs that we have. | ||||
|  */ | ||||
| static void cachefiles_clean_up_object(struct cachefiles_object *object, | ||||
| 				       struct cachefiles_cache *cache) | ||||
| { | ||||
| 	if (test_bit(FSCACHE_COOKIE_RETIRED, &object->cookie->flags)) { | ||||
| 		if (!test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags)) { | ||||
| 			cachefiles_see_object(object, cachefiles_obj_see_clean_delete); | ||||
| 			_debug("- inval object OBJ%x", object->debug_id); | ||||
| 			cachefiles_delete_object(object, FSCACHE_OBJECT_WAS_RETIRED); | ||||
| 		} else { | ||||
| 			cachefiles_see_object(object, cachefiles_obj_see_clean_drop_tmp); | ||||
| 			_debug("- inval object OBJ%x tmpfile", object->debug_id); | ||||
| 		} | ||||
| 	} else { | ||||
| 		cachefiles_see_object(object, cachefiles_obj_see_clean_commit); | ||||
| 		cachefiles_commit_object(object, cache); | ||||
| 	} | ||||
| 
 | ||||
| 	cachefiles_unmark_inode_in_use(object, object->file); | ||||
| 	if (object->file) { | ||||
| 		fput(object->file); | ||||
| 		object->file = NULL; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Withdraw caching for a cookie. | ||||
|  */ | ||||
| static void cachefiles_withdraw_cookie(struct fscache_cookie *cookie) | ||||
| { | ||||
| 	struct cachefiles_object *object = cookie->cache_priv; | ||||
| 	struct cachefiles_cache *cache = object->volume->cache; | ||||
| 	const struct cred *saved_cred; | ||||
| 
 | ||||
| 	_enter("o=%x", object->debug_id); | ||||
| 	cachefiles_see_object(object, cachefiles_obj_see_withdraw_cookie); | ||||
| 
 | ||||
| 	if (!list_empty(&object->cache_link)) { | ||||
| 		spin_lock(&cache->object_list_lock); | ||||
| 		cachefiles_see_object(object, cachefiles_obj_see_withdrawal); | ||||
| 		list_del_init(&object->cache_link); | ||||
| 		spin_unlock(&cache->object_list_lock); | ||||
| 	} | ||||
| 
 | ||||
| 	if (object->file) { | ||||
| 		cachefiles_begin_secure(cache, &saved_cred); | ||||
| 		cachefiles_clean_up_object(object, cache); | ||||
| 		cachefiles_end_secure(cache, saved_cred); | ||||
| 	} | ||||
| 
 | ||||
| 	cookie->cache_priv = NULL; | ||||
| 	cachefiles_put_object(object, cachefiles_obj_put_detach); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Invalidate the storage associated with a cookie. | ||||
|  */ | ||||
| static bool cachefiles_invalidate_cookie(struct fscache_cookie *cookie) | ||||
| { | ||||
| 	struct cachefiles_object *object = cookie->cache_priv; | ||||
| 	struct file *new_file, *old_file; | ||||
| 	bool old_tmpfile; | ||||
| 
 | ||||
| 	_enter("o=%x,[%llu]", object->debug_id, object->cookie->object_size); | ||||
| 
 | ||||
| 	old_tmpfile = test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags); | ||||
| 
 | ||||
| 	if (!object->file) { | ||||
| 		fscache_resume_after_invalidation(cookie); | ||||
| 		_leave(" = t [light]"); | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	new_file = cachefiles_create_tmpfile(object); | ||||
| 	if (IS_ERR(new_file)) | ||||
| 		goto failed; | ||||
| 
 | ||||
| 	/* Substitute the VFS target */ | ||||
| 	_debug("sub"); | ||||
| 	spin_lock(&object->lock); | ||||
| 
 | ||||
| 	old_file = object->file; | ||||
| 	object->file = new_file; | ||||
| 	object->content_info = CACHEFILES_CONTENT_NO_DATA; | ||||
| 	set_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags); | ||||
| 	set_bit(FSCACHE_COOKIE_NEEDS_UPDATE, &object->cookie->flags); | ||||
| 
 | ||||
| 	spin_unlock(&object->lock); | ||||
| 	_debug("subbed"); | ||||
| 
 | ||||
| 	/* Allow I/O to take place again */ | ||||
| 	fscache_resume_after_invalidation(cookie); | ||||
| 
 | ||||
| 	if (old_file) { | ||||
| 		if (!old_tmpfile) { | ||||
| 			struct cachefiles_volume *volume = object->volume; | ||||
| 			struct dentry *fan = volume->fanout[(u8)cookie->key_hash]; | ||||
| 
 | ||||
| 			inode_lock_nested(d_inode(fan), I_MUTEX_PARENT); | ||||
| 			cachefiles_bury_object(volume->cache, object, fan, | ||||
| 					       old_file->f_path.dentry, | ||||
| 					       FSCACHE_OBJECT_INVALIDATED); | ||||
| 		} | ||||
| 		fput(old_file); | ||||
| 	} | ||||
| 
 | ||||
| 	_leave(" = t"); | ||||
| 	return true; | ||||
| 
 | ||||
| failed: | ||||
| 	_leave(" = f"); | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| const struct fscache_cache_ops cachefiles_cache_ops = { | ||||
| 	.name			= "cachefiles", | ||||
| 	.acquire_volume		= cachefiles_acquire_volume, | ||||
| 	.free_volume		= cachefiles_free_volume, | ||||
| 	.lookup_cookie		= cachefiles_lookup_cookie, | ||||
| 	.withdraw_cookie	= cachefiles_withdraw_cookie, | ||||
| 	.invalidate_cookie	= cachefiles_invalidate_cookie, | ||||
| 	.prepare_to_write	= cachefiles_prepare_to_write, | ||||
| }; | ||||
|  |  | |||
|  | @ -16,6 +16,8 @@ | |||
| #include <linux/cred.h> | ||||
| #include <linux/security.h> | ||||
| 
 | ||||
| #define CACHEFILES_DIO_BLOCK_SIZE 4096 | ||||
| 
 | ||||
| struct cachefiles_cache; | ||||
| struct cachefiles_object; | ||||
| 
 | ||||
|  | @ -68,6 +70,7 @@ struct cachefiles_cache { | |||
| 	struct dentry			*graveyard;	/* directory into which dead objects go */ | ||||
| 	struct file			*cachefilesd;	/* manager daemon handle */ | ||||
| 	struct list_head		volumes;	/* List of volume objects */ | ||||
| 	struct list_head		object_list;	/* List of active objects */ | ||||
| 	spinlock_t			object_list_lock; /* Lock for volumes and object_list */ | ||||
| 	const struct cred		*cache_cred;	/* security override for accessing cache */ | ||||
| 	struct mutex			daemon_mutex;	/* command serialisation mutex */ | ||||
|  | @ -194,6 +197,9 @@ extern int cachefiles_bury_object(struct cachefiles_cache *cache, | |||
| 				  struct dentry *dir, | ||||
| 				  struct dentry *rep, | ||||
| 				  enum fscache_why_object_killed why); | ||||
| extern int cachefiles_delete_object(struct cachefiles_object *object, | ||||
| 				    enum fscache_why_object_killed why); | ||||
| extern bool cachefiles_look_up_object(struct cachefiles_object *object); | ||||
| extern struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache, | ||||
| 					       struct dentry *dir, | ||||
| 					       const char *name, | ||||
|  | @ -205,6 +211,9 @@ extern int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir, | |||
| 
 | ||||
| extern int cachefiles_check_in_use(struct cachefiles_cache *cache, | ||||
| 				   struct dentry *dir, char *filename); | ||||
| extern struct file *cachefiles_create_tmpfile(struct cachefiles_object *object); | ||||
| extern bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache, | ||||
| 				      struct cachefiles_object *object); | ||||
| 
 | ||||
| /*
 | ||||
|  * security.c | ||||
|  |  | |||
|  | @ -404,6 +404,324 @@ int cachefiles_bury_object(struct cachefiles_cache *cache, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Delete a cache file. | ||||
|  */ | ||||
| int cachefiles_delete_object(struct cachefiles_object *object, | ||||
| 			     enum fscache_why_object_killed why) | ||||
| { | ||||
| 	struct cachefiles_volume *volume = object->volume; | ||||
| 	struct dentry *dentry = object->file->f_path.dentry; | ||||
| 	struct dentry *fan = volume->fanout[(u8)object->cookie->key_hash]; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	_enter(",OBJ%x{%pD}", object->debug_id, object->file); | ||||
| 
 | ||||
| 	/* Stop the dentry being negated if it's only pinned by a file struct. */ | ||||
| 	dget(dentry); | ||||
| 
 | ||||
| 	inode_lock_nested(d_backing_inode(fan), I_MUTEX_PARENT); | ||||
| 	ret = cachefiles_unlink(volume->cache, object, fan, dentry, why); | ||||
| 	inode_unlock(d_backing_inode(fan)); | ||||
| 	dput(dentry); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Create a temporary file and leave it unattached and un-xattr'd until the | ||||
|  * time comes to discard the object from memory. | ||||
|  */ | ||||
| struct file *cachefiles_create_tmpfile(struct cachefiles_object *object) | ||||
| { | ||||
| 	struct cachefiles_volume *volume = object->volume; | ||||
| 	struct cachefiles_cache *cache = volume->cache; | ||||
| 	const struct cred *saved_cred; | ||||
| 	struct dentry *fan = volume->fanout[(u8)object->cookie->key_hash]; | ||||
| 	struct file *file; | ||||
| 	struct path path; | ||||
| 	uint64_t ni_size = object->cookie->object_size; | ||||
| 	long ret; | ||||
| 
 | ||||
| 	ni_size = round_up(ni_size, CACHEFILES_DIO_BLOCK_SIZE); | ||||
| 
 | ||||
| 	cachefiles_begin_secure(cache, &saved_cred); | ||||
| 
 | ||||
| 	path.mnt = cache->mnt; | ||||
| 	ret = cachefiles_inject_write_error(); | ||||
| 	if (ret == 0) | ||||
| 		path.dentry = vfs_tmpfile(&init_user_ns, fan, S_IFREG, O_RDWR); | ||||
| 	else | ||||
| 		path.dentry = ERR_PTR(ret); | ||||
| 	if (IS_ERR(path.dentry)) { | ||||
| 		trace_cachefiles_vfs_error(object, d_inode(fan), PTR_ERR(path.dentry), | ||||
| 					   cachefiles_trace_tmpfile_error); | ||||
| 		if (PTR_ERR(path.dentry) == -EIO) | ||||
| 			cachefiles_io_error_obj(object, "Failed to create tmpfile"); | ||||
| 		file = ERR_CAST(path.dentry); | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	trace_cachefiles_tmpfile(object, d_backing_inode(path.dentry)); | ||||
| 
 | ||||
| 	if (!cachefiles_mark_inode_in_use(object, path.dentry)) { | ||||
| 		file = ERR_PTR(-EBUSY); | ||||
| 		goto out_dput; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ni_size > 0) { | ||||
| 		trace_cachefiles_trunc(object, d_backing_inode(path.dentry), 0, ni_size, | ||||
| 				       cachefiles_trunc_expand_tmpfile); | ||||
| 		ret = cachefiles_inject_write_error(); | ||||
| 		if (ret == 0) | ||||
| 			ret = vfs_truncate(&path, ni_size); | ||||
| 		if (ret < 0) { | ||||
| 			trace_cachefiles_vfs_error( | ||||
| 				object, d_backing_inode(path.dentry), ret, | ||||
| 				cachefiles_trace_trunc_error); | ||||
| 			file = ERR_PTR(ret); | ||||
| 			goto out_dput; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	file = open_with_fake_path(&path, O_RDWR | O_LARGEFILE | O_DIRECT, | ||||
| 				   d_backing_inode(path.dentry), cache->cache_cred); | ||||
| 	if (IS_ERR(file)) { | ||||
| 		trace_cachefiles_vfs_error(object, d_backing_inode(path.dentry), | ||||
| 					   PTR_ERR(file), | ||||
| 					   cachefiles_trace_open_error); | ||||
| 		goto out_dput; | ||||
| 	} | ||||
| 	if (unlikely(!file->f_op->read_iter) || | ||||
| 	    unlikely(!file->f_op->write_iter)) { | ||||
| 		fput(file); | ||||
| 		pr_notice("Cache does not support read_iter and write_iter\n"); | ||||
| 		file = ERR_PTR(-EINVAL); | ||||
| 	} | ||||
| 
 | ||||
| out_dput: | ||||
| 	dput(path.dentry); | ||||
| out: | ||||
| 	cachefiles_end_secure(cache, saved_cred); | ||||
| 	return file; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Create a new file. | ||||
|  */ | ||||
| static bool cachefiles_create_file(struct cachefiles_object *object) | ||||
| { | ||||
| 	struct file *file; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = cachefiles_has_space(object->volume->cache, 1, 0); | ||||
| 	if (ret < 0) | ||||
| 		return false; | ||||
| 
 | ||||
| 	file = cachefiles_create_tmpfile(object); | ||||
| 	if (IS_ERR(file)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	set_bit(FSCACHE_COOKIE_NEEDS_UPDATE, &object->cookie->flags); | ||||
| 	set_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags); | ||||
| 	_debug("create -> %pD{ino=%lu}", file, file_inode(file)->i_ino); | ||||
| 	object->file = file; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Open an existing file, checking its attributes and replacing it if it is | ||||
|  * stale. | ||||
|  */ | ||||
| static bool cachefiles_open_file(struct cachefiles_object *object, | ||||
| 				 struct dentry *dentry) | ||||
| { | ||||
| 	struct cachefiles_cache *cache = object->volume->cache; | ||||
| 	struct file *file; | ||||
| 	struct path path; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	_enter("%pd", dentry); | ||||
| 
 | ||||
| 	if (!cachefiles_mark_inode_in_use(object, dentry)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	/* We need to open a file interface onto a data file now as we can't do
 | ||||
| 	 * it on demand because writeback called from do_exit() sees | ||||
| 	 * current->fs == NULL - which breaks d_path() called from ext4 open. | ||||
| 	 */ | ||||
| 	path.mnt = cache->mnt; | ||||
| 	path.dentry = dentry; | ||||
| 	file = open_with_fake_path(&path, O_RDWR | O_LARGEFILE | O_DIRECT, | ||||
| 				   d_backing_inode(dentry), cache->cache_cred); | ||||
| 	if (IS_ERR(file)) { | ||||
| 		trace_cachefiles_vfs_error(object, d_backing_inode(dentry), | ||||
| 					   PTR_ERR(file), | ||||
| 					   cachefiles_trace_open_error); | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	if (unlikely(!file->f_op->read_iter) || | ||||
| 	    unlikely(!file->f_op->write_iter)) { | ||||
| 		pr_notice("Cache does not support read_iter and write_iter\n"); | ||||
| 		goto error_fput; | ||||
| 	} | ||||
| 	_debug("file -> %pd positive", dentry); | ||||
| 
 | ||||
| 	ret = cachefiles_check_auxdata(object, file); | ||||
| 	if (ret < 0) | ||||
| 		goto check_failed; | ||||
| 
 | ||||
| 	object->file = file; | ||||
| 
 | ||||
| 	/* Always update the atime on an object we've just looked up (this is
 | ||||
| 	 * used to keep track of culling, and atimes are only updated by read, | ||||
| 	 * write and readdir but not lookup or open). | ||||
| 	 */ | ||||
| 	touch_atime(&file->f_path); | ||||
| 	dput(dentry); | ||||
| 	return true; | ||||
| 
 | ||||
| check_failed: | ||||
| 	fscache_cookie_lookup_negative(object->cookie); | ||||
| 	cachefiles_unmark_inode_in_use(object, file); | ||||
| 	if (ret == -ESTALE) { | ||||
| 		fput(file); | ||||
| 		dput(dentry); | ||||
| 		return cachefiles_create_file(object); | ||||
| 	} | ||||
| error_fput: | ||||
| 	fput(file); | ||||
| error: | ||||
| 	dput(dentry); | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * walk from the parent object to the child object through the backing | ||||
|  * filesystem, creating directories as we go | ||||
|  */ | ||||
| bool cachefiles_look_up_object(struct cachefiles_object *object) | ||||
| { | ||||
| 	struct cachefiles_volume *volume = object->volume; | ||||
| 	struct dentry *dentry, *fan = volume->fanout[(u8)object->cookie->key_hash]; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	_enter("OBJ%x,%s,", object->debug_id, object->d_name); | ||||
| 
 | ||||
| 	/* Look up path "cache/vol/fanout/file". */ | ||||
| 	ret = cachefiles_inject_read_error(); | ||||
| 	if (ret == 0) | ||||
| 		dentry = lookup_positive_unlocked(object->d_name, fan, | ||||
| 						  object->d_name_len); | ||||
| 	else | ||||
| 		dentry = ERR_PTR(ret); | ||||
| 	trace_cachefiles_lookup(object, dentry); | ||||
| 	if (IS_ERR(dentry)) { | ||||
| 		if (dentry == ERR_PTR(-ENOENT)) | ||||
| 			goto new_file; | ||||
| 		if (dentry == ERR_PTR(-EIO)) | ||||
| 			cachefiles_io_error_obj(object, "Lookup failed"); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!d_is_reg(dentry)) { | ||||
| 		pr_err("%pd is not a file\n", dentry); | ||||
| 		inode_lock_nested(d_inode(fan), I_MUTEX_PARENT); | ||||
| 		ret = cachefiles_bury_object(volume->cache, object, fan, dentry, | ||||
| 					     FSCACHE_OBJECT_IS_WEIRD); | ||||
| 		dput(dentry); | ||||
| 		if (ret < 0) | ||||
| 			return false; | ||||
| 		goto new_file; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!cachefiles_open_file(object, dentry)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	_leave(" = t [%lu]", file_inode(object->file)->i_ino); | ||||
| 	return true; | ||||
| 
 | ||||
| new_file: | ||||
| 	fscache_cookie_lookup_negative(object->cookie); | ||||
| 	return cachefiles_create_file(object); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Attempt to link a temporary file into its rightful place in the cache. | ||||
|  */ | ||||
| bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache, | ||||
| 			       struct cachefiles_object *object) | ||||
| { | ||||
| 	struct cachefiles_volume *volume = object->volume; | ||||
| 	struct dentry *dentry, *fan = volume->fanout[(u8)object->cookie->key_hash]; | ||||
| 	bool success = false; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	_enter(",%pD", object->file); | ||||
| 
 | ||||
| 	inode_lock_nested(d_inode(fan), I_MUTEX_PARENT); | ||||
| 	ret = cachefiles_inject_read_error(); | ||||
| 	if (ret == 0) | ||||
| 		dentry = lookup_one_len(object->d_name, fan, object->d_name_len); | ||||
| 	else | ||||
| 		dentry = ERR_PTR(ret); | ||||
| 	if (IS_ERR(dentry)) { | ||||
| 		trace_cachefiles_vfs_error(object, d_inode(fan), PTR_ERR(dentry), | ||||
| 					   cachefiles_trace_lookup_error); | ||||
| 		_debug("lookup fail %ld", PTR_ERR(dentry)); | ||||
| 		goto out_unlock; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!d_is_negative(dentry)) { | ||||
| 		if (d_backing_inode(dentry) == file_inode(object->file)) { | ||||
| 			success = true; | ||||
| 			goto out_dput; | ||||
| 		} | ||||
| 
 | ||||
| 		ret = cachefiles_unlink(volume->cache, object, fan, dentry, | ||||
| 					FSCACHE_OBJECT_IS_STALE); | ||||
| 		if (ret < 0) | ||||
| 			goto out_dput; | ||||
| 
 | ||||
| 		dput(dentry); | ||||
| 		ret = cachefiles_inject_read_error(); | ||||
| 		if (ret == 0) | ||||
| 			dentry = lookup_one_len(object->d_name, fan, object->d_name_len); | ||||
| 		else | ||||
| 			dentry = ERR_PTR(ret); | ||||
| 		if (IS_ERR(dentry)) { | ||||
| 			trace_cachefiles_vfs_error(object, d_inode(fan), PTR_ERR(dentry), | ||||
| 						   cachefiles_trace_lookup_error); | ||||
| 			_debug("lookup fail %ld", PTR_ERR(dentry)); | ||||
| 			goto out_unlock; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	ret = cachefiles_inject_read_error(); | ||||
| 	if (ret == 0) | ||||
| 		ret = vfs_link(object->file->f_path.dentry, &init_user_ns, | ||||
| 			       d_inode(fan), dentry, NULL); | ||||
| 	if (ret < 0) { | ||||
| 		trace_cachefiles_vfs_error(object, d_inode(fan), ret, | ||||
| 					   cachefiles_trace_link_error); | ||||
| 		_debug("link fail %d", ret); | ||||
| 	} else { | ||||
| 		trace_cachefiles_link(object, file_inode(object->file)); | ||||
| 		spin_lock(&object->lock); | ||||
| 		/* TODO: Do we want to switch the file pointer to the new dentry? */ | ||||
| 		clear_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags); | ||||
| 		spin_unlock(&object->lock); | ||||
| 		success = true; | ||||
| 	} | ||||
| 
 | ||||
| out_dput: | ||||
| 	dput(dentry); | ||||
| out_unlock: | ||||
| 	inode_unlock(d_inode(fan)); | ||||
| 	_leave(" = %u", success); | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Look up an inode to be checked or culled.  Return -EBUSY if the inode is | ||||
|  * marked in use. | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 David Howells
						David Howells