824 lines · 20.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* CacheFiles path walking and related routines * * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) */ #include <linux/fs.h>#include <linux/namei.h>#include "internal.h" /* * Mark the backing file as being a cache file if it's not already in use.  The * mark tells the culling request command that it's not allowed to cull the * file or directory.  The caller must hold the inode lock. */static bool __cachefiles_mark_inode_in_use(struct cachefiles_object *object,					   struct inode *inode){	bool can_use = false; 	if (!(inode->i_flags & S_KERNEL_FILE)) {		inode->i_flags |= S_KERNEL_FILE;		trace_cachefiles_mark_active(object, inode);		can_use = true;	} else {		trace_cachefiles_mark_failed(object, inode);	} 	return can_use;} static bool cachefiles_mark_inode_in_use(struct cachefiles_object *object,					 struct inode *inode){	bool can_use; 	inode_lock(inode);	can_use = __cachefiles_mark_inode_in_use(object, inode);	inode_unlock(inode);	return can_use;} /* * Unmark a backing inode.  The caller must hold the inode lock. */static void __cachefiles_unmark_inode_in_use(struct cachefiles_object *object,					     struct inode *inode){	inode->i_flags &= ~S_KERNEL_FILE;	trace_cachefiles_mark_inactive(object, inode);} static void cachefiles_do_unmark_inode_in_use(struct cachefiles_object *object,					      struct inode *inode){	inode_lock(inode);	__cachefiles_unmark_inode_in_use(object, inode);	inode_unlock(inode);} /* * Unmark a backing inode and tell cachefilesd that there's something that can * be culled. */void cachefiles_unmark_inode_in_use(struct cachefiles_object *object,				    struct file *file){	struct cachefiles_cache *cache = object->volume->cache;	struct inode *inode = file_inode(file); 	cachefiles_do_unmark_inode_in_use(object, inode); 	if (!test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags)) {		atomic_long_add(inode->i_blocks, &cache->b_released);		if (atomic_inc_return(&cache->f_released))			cachefiles_state_changed(cache);	}} /* * get a subdirectory */struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,					struct dentry *dir,					const char *dirname,					bool *_is_new){	struct dentry *subdir;	struct path path;	int ret; 	_enter(",,%s", dirname); 	/* search the current directory for the element name */ retry:	ret = cachefiles_inject_read_error();	if (ret == 0)		subdir = start_creating(&nop_mnt_idmap, dir, &QSTR(dirname));	else		subdir = ERR_PTR(ret);	trace_cachefiles_lookup(NULL, dir, subdir);	if (IS_ERR(subdir)) {		trace_cachefiles_vfs_error(NULL, d_backing_inode(dir),					   PTR_ERR(subdir),					   cachefiles_trace_lookup_error);		if (PTR_ERR(subdir) == -ENOMEM)			goto nomem_d_alloc;		goto lookup_error;	} 	_debug("subdir -> %pd %s",	       subdir, d_backing_inode(subdir) ? "positive" : "negative"); 	/* we need to create the subdir if it doesn't exist yet */	if (d_is_negative(subdir)) {		ret = cachefiles_has_space(cache, 1, 0,					   cachefiles_has_space_for_create);		if (ret < 0)			goto mkdir_error; 		_debug("attempt mkdir"); 		path.mnt = cache->mnt;		path.dentry = dir;		ret = security_path_mkdir(&path, subdir, 0700);		if (ret < 0)			goto mkdir_error;		ret = cachefiles_inject_write_error();		if (ret == 0) {			subdir = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), subdir, 0700, NULL);			if (IS_ERR(subdir))				ret = PTR_ERR(subdir);		} else {			end_creating(subdir);			subdir = ERR_PTR(ret);		}		if (IS_ERR(subdir)) {			trace_cachefiles_vfs_error(NULL, d_inode(dir), ret,						   cachefiles_trace_mkdir_error);			goto mkdir_error;		}		trace_cachefiles_mkdir(dir, subdir); 		if (unlikely(d_unhashed(subdir) || d_is_negative(subdir))) {			end_creating(subdir);			goto retry;		}		ASSERT(d_backing_inode(subdir)); 		_debug("mkdir -> %pd{ino=%llu}",		       subdir, d_backing_inode(subdir)->i_ino);		if (_is_new)			*_is_new = true;	} 	/* Tell rmdir() it's not allowed to delete the subdir */	inode_lock(d_inode(subdir));	end_creating_keep(subdir); 	if (!__cachefiles_mark_inode_in_use(NULL, d_inode(subdir))) {		pr_notice("cachefiles: Inode already in use: %pd (B=%llx)\n",			  subdir, d_inode(subdir)->i_ino);		goto mark_error;	} 	inode_unlock(d_inode(subdir)); 	/* we need to make sure the subdir is a directory */	ASSERT(d_backing_inode(subdir)); 	if (!d_can_lookup(subdir)) {		pr_err("%s is not a directory\n", dirname);		ret = -EIO;		goto check_error;	} 	ret = -EPERM;	if (!(d_backing_inode(subdir)->i_opflags & IOP_XATTR) ||	    !d_backing_inode(subdir)->i_op->lookup ||	    !d_backing_inode(subdir)->i_op->mkdir ||	    !d_backing_inode(subdir)->i_op->rename ||	    !d_backing_inode(subdir)->i_op->rmdir ||	    !d_backing_inode(subdir)->i_op->unlink)		goto check_error; 	_leave(" = [%llu]", d_backing_inode(subdir)->i_ino);	return subdir; check_error:	cachefiles_put_directory(subdir);	_leave(" = %d [check]", ret);	return ERR_PTR(ret); mark_error:	inode_unlock(d_inode(subdir));	dput(subdir);	return ERR_PTR(-EBUSY); mkdir_error:	end_creating(subdir);	pr_err("mkdir %s failed with error %d\n", dirname, ret);	return ERR_PTR(ret); lookup_error:	ret = PTR_ERR(subdir);	pr_err("Lookup %s failed with error %d\n", dirname, ret);	return ERR_PTR(ret); nomem_d_alloc:	_leave(" = -ENOMEM");	return ERR_PTR(-ENOMEM);} /* * Put a subdirectory. */void cachefiles_put_directory(struct dentry *dir){	if (dir) {		cachefiles_do_unmark_inode_in_use(NULL, d_inode(dir));		dput(dir);	}} /* * Remove a regular file from the cache. */static int cachefiles_unlink(struct cachefiles_cache *cache,			     struct cachefiles_object *object,			     struct dentry *dir, struct dentry *dentry,			     enum fscache_why_object_killed why){	struct path path = {		.mnt	= cache->mnt,		.dentry	= dir,	};	int ret; 	trace_cachefiles_unlink(object, d_inode(dentry)->i_ino, why);	ret = security_path_unlink(&path, dentry);	if (ret < 0) {		cachefiles_io_error(cache, "Unlink security error");		return ret;	} 	ret = cachefiles_inject_remove_error();	if (ret == 0) {		ret = vfs_unlink(&nop_mnt_idmap, d_backing_inode(dir), dentry, NULL);		if (ret == -EIO)			cachefiles_io_error(cache, "Unlink failed");	}	if (ret != 0)		trace_cachefiles_vfs_error(object, d_backing_inode(dir), ret,					   cachefiles_trace_unlink_error);	return ret;} /* * Delete an object representation from the cache * - File backed objects are unlinked * - Directory backed objects are stuffed into the graveyard for userspace to *   delete * On entry dir must be locked.  It will be unlocked on exit. * On entry there must be at least 2 refs on rep, one will be dropped on exit. */int cachefiles_bury_object(struct cachefiles_cache *cache,			   struct cachefiles_object *object,			   struct dentry *dir,			   struct dentry *rep,			   enum fscache_why_object_killed why){	struct dentry *grave;	struct renamedata rd = {};	struct path path, path_to_graveyard;	char nbuffer[8 + 8 + 1];	int ret; 	_enter(",'%pd','%pd'", dir, rep); 	if (rep->d_parent != dir) {		end_removing(rep);		_leave(" = -ESTALE");		return -ESTALE;	} 	/* non-directories can just be unlinked */	if (!d_is_dir(rep)) {		ret = cachefiles_unlink(cache, object, dir, rep, why);		end_removing(rep); 		_leave(" = %d", ret);		return ret;	} 	/* directories have to be moved to the graveyard */	_debug("move stale object to graveyard");	end_removing(rep); try_again:	/* first step is to make up a grave dentry in the graveyard */	sprintf(nbuffer, "%08x%08x",		(uint32_t) ktime_get_real_seconds(),		(uint32_t) atomic_inc_return(&cache->gravecounter)); 	rd.mnt_idmap = &nop_mnt_idmap;	rd.old_parent = dir;	rd.new_parent = cache->graveyard;	rd.flags = 0;	ret = start_renaming_dentry(&rd, 0, rep, &QSTR(nbuffer));	if (ret) {		/* Some errors aren't fatal */		if (ret == -EXDEV)			/* double-lock failed */			return ret;		if (d_unhashed(rep) || rep->d_parent != dir || IS_DEADDIR(d_inode(rep))) {			/* the entry was probably culled when we dropped the parent dir			 * lock */			_leave(" = 0 [culled?]");			return 0;		}		if (ret == -EINVAL || ret == -ENOTEMPTY) {			cachefiles_io_error(cache, "May not make directory loop");			return -EIO;		}		if (ret == -ENOMEM) {			_leave(" = -ENOMEM");			return -ENOMEM;		} 		cachefiles_io_error(cache, "Lookup error %d", ret);		return -EIO;	} 	if (d_mountpoint(rep)) {		end_renaming(&rd);		cachefiles_io_error(cache, "Mountpoint in cache");		return -EIO;	} 	grave = rd.new_dentry;	if (d_is_positive(grave)) {		end_renaming(&rd);		grave = NULL;		cond_resched();		goto try_again;	} 	if (d_mountpoint(grave)) {		end_renaming(&rd);		cachefiles_io_error(cache, "Mountpoint in graveyard");		return -EIO;	} 	/* attempt the rename */	path.mnt = cache->mnt;	path.dentry = dir;	path_to_graveyard.mnt = cache->mnt;	path_to_graveyard.dentry = cache->graveyard;	ret = security_path_rename(&path, rep, &path_to_graveyard, grave, 0);	if (ret < 0) {		cachefiles_io_error(cache, "Rename security error %d", ret);	} else {		trace_cachefiles_rename(object, d_inode(rep)->i_ino, why);		ret = cachefiles_inject_read_error();		if (ret == 0)			ret = vfs_rename(&rd);		if (ret != 0)			trace_cachefiles_vfs_error(object, d_inode(dir), ret,						   cachefiles_trace_rename_error);		if (ret != 0 && ret != -ENOMEM)			cachefiles_io_error(cache,					    "Rename failed with error %d", ret);	} 	cachefiles_do_unmark_inode_in_use(object, d_inode(rep));	end_renaming(&rd);	_leave(" = 0");	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); 	dentry = start_removing_dentry(fan, dentry);	if (IS_ERR(dentry))		ret = PTR_ERR(dentry);	else		ret = cachefiles_unlink(volume->cache, object, fan, dentry, why);	end_removing(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;	const struct path parentpath = { .mnt = cache->mnt, .dentry = fan };	uint64_t ni_size;	long ret;  	cachefiles_begin_secure(cache, &saved_cred); 	ret = cachefiles_inject_write_error();	if (ret == 0) {		file = kernel_tmpfile_open(&nop_mnt_idmap, &parentpath,					   S_IFREG | 0600,					   O_RDWR | O_LARGEFILE | O_DIRECT,					   cache->cache_cred);		ret = PTR_ERR_OR_ZERO(file);	}	if (ret) {		trace_cachefiles_vfs_error(object, d_inode(fan), ret,					   cachefiles_trace_tmpfile_error);		if (ret == -EIO)			cachefiles_io_error_obj(object, "Failed to create tmpfile");		goto err;	} 	trace_cachefiles_tmpfile(object, file_inode(file)); 	/* This is a newly created file with no other possible user */	if (!cachefiles_mark_inode_in_use(object, file_inode(file)))		WARN_ON(1); 	ni_size = object->cookie->object_size;	ni_size = round_up(ni_size, CACHEFILES_DIO_BLOCK_SIZE); 	if (ni_size > 0) {		trace_cachefiles_trunc(object, file_inode(file), 0, ni_size,				       cachefiles_trunc_expand_tmpfile);		ret = cachefiles_inject_write_error();		if (ret == 0)			ret = vfs_truncate(&file->f_path, ni_size);		if (ret < 0) {			trace_cachefiles_vfs_error(				object, file_inode(file), ret,				cachefiles_trace_trunc_error);			goto err_unuse;		}	} 	ret = -EINVAL;	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 err_unuse;	}out:	cachefiles_end_secure(cache, saved_cred);	return file; err_unuse:	cachefiles_do_unmark_inode_in_use(object, file_inode(file));	fput(file);err:	file = ERR_PTR(ret);	goto out;} /* * 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,				   cachefiles_has_space_for_create);	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=%llu}", file, file_inode(file)->i_ino);	object->file = file;

Lines 1–500 of 824.