2508 lines · 62.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/*  FUSE: Filesystem in Userspace  Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>*/ #include "dev.h"#include "fuse_i.h" #include <linux/pagemap.h>#include <linux/file.h>#include <linux/fs_context.h>#include <linux/moduleparam.h>#include <linux/sched.h>#include <linux/namei.h>#include <linux/slab.h>#include <linux/xattr.h>#include <linux/iversion.h>#include <linux/posix_acl.h>#include <linux/security.h>#include <linux/types.h>#include <linux/kernel.h> static bool __read_mostly allow_sys_admin_access;module_param(allow_sys_admin_access, bool, 0644);MODULE_PARM_DESC(allow_sys_admin_access,		 "Allow users with CAP_SYS_ADMIN in initial userns to bypass allow_other access check"); struct dentry_bucket {	struct rb_root tree;	spinlock_t lock;}; #define FUSE_HASH_BITS	5#define FUSE_HASH_SIZE	(1 << FUSE_HASH_BITS)static struct dentry_bucket dentry_hash[FUSE_HASH_SIZE];static struct delayed_work dentry_tree_work; /* Minimum invalidation work queue frequency */#define FUSE_DENTRY_INVAL_FREQ_MIN 5 unsigned __read_mostly inval_wq;static int inval_wq_set(const char *val, const struct kernel_param *kp){	unsigned int num;	unsigned int old = inval_wq;	int ret; 	if (!val)		return -EINVAL; 	ret = kstrtouint(val, 0, &num);	if (ret)		return ret; 	if ((num < FUSE_DENTRY_INVAL_FREQ_MIN) && (num != 0))		return -EINVAL; 	/* This should prevent overflow in secs_to_jiffies() */	if (num > USHRT_MAX)		return -EINVAL; 	*((unsigned int *)kp->arg) = num; 	if (num && !old)		schedule_delayed_work(&dentry_tree_work,				      secs_to_jiffies(num));	else if (!num && old)		cancel_delayed_work_sync(&dentry_tree_work); 	return 0;}static const struct kernel_param_ops inval_wq_ops = {	.set = inval_wq_set,	.get = param_get_uint,};module_param_cb(inval_wq, &inval_wq_ops, &inval_wq, 0644);__MODULE_PARM_TYPE(inval_wq, "uint");MODULE_PARM_DESC(inval_wq,		 "Dentries invalidation work queue period in secs (>= "		 __stringify(FUSE_DENTRY_INVAL_FREQ_MIN) ")."); static inline struct dentry_bucket *get_dentry_bucket(struct dentry *dentry){	int i = hash_ptr(dentry, FUSE_HASH_BITS); 	return &dentry_hash[i];} static void fuse_advise_use_readdirplus(struct inode *dir){	struct fuse_inode *fi = get_fuse_inode(dir); 	set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state);} struct fuse_dentry {	u64 time;	u64 epoch;	union {		struct rcu_head rcu;		struct rb_node node;	};	struct dentry *dentry;}; static void __fuse_dentry_tree_del_node(struct fuse_dentry *fd,					struct dentry_bucket *bucket){	if (!RB_EMPTY_NODE(&fd->node)) {		rb_erase(&fd->node, &bucket->tree);		RB_CLEAR_NODE(&fd->node);	}} static void fuse_dentry_tree_del_node(struct dentry *dentry){	struct fuse_dentry *fd = dentry->d_fsdata;	struct dentry_bucket *bucket = get_dentry_bucket(dentry); 	spin_lock(&bucket->lock);	__fuse_dentry_tree_del_node(fd, bucket);	spin_unlock(&bucket->lock);} static void fuse_dentry_tree_add_node(struct dentry *dentry){	struct fuse_dentry *fd = dentry->d_fsdata;	struct dentry_bucket *bucket;	struct fuse_dentry *cur;	struct rb_node **p, *parent = NULL; 	if (!inval_wq)		return; 	bucket = get_dentry_bucket(dentry); 	spin_lock(&bucket->lock); 	__fuse_dentry_tree_del_node(fd, bucket); 	p = &bucket->tree.rb_node;	while (*p) {		parent = *p;		cur = rb_entry(*p, struct fuse_dentry, node);		if (fd->time < cur->time)			p = &(*p)->rb_left;		else			p = &(*p)->rb_right;	}	rb_link_node(&fd->node, parent, p);	rb_insert_color(&fd->node, &bucket->tree);	spin_unlock(&bucket->lock);} /* * work queue which, when enabled, will periodically check for expired dentries * in the dentries tree. */static void fuse_dentry_tree_work(struct work_struct *work){	LIST_HEAD(dispose);	struct fuse_dentry *fd;	struct rb_node *node;	int i; 	for (i = 0; i < FUSE_HASH_SIZE; i++) {		spin_lock(&dentry_hash[i].lock);		node = rb_first(&dentry_hash[i].tree);		while (node) {			fd = rb_entry(node, struct fuse_dentry, node);			if (!time_before64(fd->time, get_jiffies_64()))				break; 			rb_erase(&fd->node, &dentry_hash[i].tree);			RB_CLEAR_NODE(&fd->node);			spin_lock(&fd->dentry->d_lock);			/* If dentry is still referenced, let next dput release it */			fd->dentry->d_flags |= DCACHE_OP_DELETE;			__move_to_shrink_list(fd->dentry, &dispose);			spin_unlock(&fd->dentry->d_lock);			if (need_resched()) {				spin_unlock(&dentry_hash[i].lock);				cond_resched();				spin_lock(&dentry_hash[i].lock);			}			node = rb_first(&dentry_hash[i].tree);		}		spin_unlock(&dentry_hash[i].lock);	}	shrink_dentry_list(&dispose); 	if (inval_wq)		schedule_delayed_work(&dentry_tree_work,				      secs_to_jiffies(inval_wq));} void fuse_epoch_work(struct work_struct *work){	struct fuse_conn *fc = container_of(work, struct fuse_conn,					    epoch_work);	struct fuse_mount *fm;	struct inode *inode; 	down_read(&fc->killsb); 	inode = fuse_ilookup(fc, FUSE_ROOT_ID, &fm);	if (inode) {		iput(inode);		/* Remove all possible active references to cached inodes */		shrink_dcache_sb(fm->sb);	} else		pr_warn("Failed to get root inode"); 	up_read(&fc->killsb);} void fuse_dentry_tree_init(void){	int i; 	for (i = 0; i < FUSE_HASH_SIZE; i++) {		spin_lock_init(&dentry_hash[i].lock);		dentry_hash[i].tree = RB_ROOT;	}	INIT_DELAYED_WORK(&dentry_tree_work, fuse_dentry_tree_work);} void fuse_dentry_tree_cleanup(void){	int i; 	inval_wq = 0;	cancel_delayed_work_sync(&dentry_tree_work); 	for (i = 0; i < FUSE_HASH_SIZE; i++)		WARN_ON_ONCE(!RB_EMPTY_ROOT(&dentry_hash[i].tree));} void fuse_dentry_set_epoch(struct dentry *dentry, u64 epoch){	struct fuse_dentry *fd = dentry->d_fsdata; 	fd->epoch = epoch;} static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time){	((struct fuse_dentry *) dentry->d_fsdata)->time = time;} static inline u64 fuse_dentry_time(const struct dentry *entry){	return ((struct fuse_dentry *) entry->d_fsdata)->time;} static void fuse_dentry_settime(struct dentry *dentry, u64 time){	struct fuse_conn *fc = get_fuse_conn_super(dentry->d_sb);	bool delete = !time && fc->delete_stale;	/*	 * Mess with DCACHE_OP_DELETE because dput() will be faster without it.	 * Don't care about races, either way it's just an optimization	 */	if ((!delete && (dentry->d_flags & DCACHE_OP_DELETE)) ||	    (delete && !(dentry->d_flags & DCACHE_OP_DELETE))) {		spin_lock(&dentry->d_lock);		if (!delete)			dentry->d_flags &= ~DCACHE_OP_DELETE;		else			dentry->d_flags |= DCACHE_OP_DELETE;		spin_unlock(&dentry->d_lock);	} 	__fuse_dentry_settime(dentry, time);	fuse_dentry_tree_add_node(dentry);} /* * FUSE caches dentries and attributes with separate timeout.  The * time in jiffies until the dentry/attributes are valid is stored in * dentry->d_fsdata and fuse_inode->i_time respectively. */ /* * Calculate the time in jiffies until a dentry/attributes are valid */u64 fuse_time_to_jiffies(u64 sec, u32 nsec){	if (sec || nsec) {		struct timespec64 ts = {			sec,			min_t(u32, nsec, NSEC_PER_SEC - 1)		}; 		return get_jiffies_64() + timespec64_to_jiffies(&ts);	} else		return 0;} /* * Set dentry and possibly attribute timeouts from the lookup/mk* * replies */void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o){	fuse_dentry_settime(entry,		fuse_time_to_jiffies(o->entry_valid, o->entry_valid_nsec));} void fuse_invalidate_attr_mask(struct inode *inode, u32 mask){	set_mask_bits(&get_fuse_inode(inode)->inval_mask, 0, mask);} /* * Mark the attributes as stale, so that at the next call to * ->getattr() they will be fetched from userspace */void fuse_invalidate_attr(struct inode *inode){	fuse_invalidate_attr_mask(inode, STATX_BASIC_STATS);} static void fuse_dir_changed(struct inode *dir){	fuse_invalidate_attr_mask(dir, FUSE_STATX_MODDIR);	inode_maybe_inc_iversion(dir, false);} /* * Mark the attributes as stale due to an atime change.  Avoid the invalidate if * atime is not used. */void fuse_invalidate_atime(struct inode *inode){	if (!IS_RDONLY(inode))		fuse_invalidate_attr_mask(inode, STATX_ATIME);} /* * Just mark the entry as stale, so that a next attempt to look it up * will result in a new lookup call to userspace * * This is called when a dentry is about to become negative and the * timeout is unknown (unlink, rmdir, rename and in some cases * lookup) */void fuse_invalidate_entry_cache(struct dentry *entry){	fuse_dentry_settime(entry, 0);} /* * Same as fuse_invalidate_entry_cache(), but also try to remove the * dentry from the hash */static void fuse_invalidate_entry(struct dentry *entry){	d_invalidate(entry);	fuse_invalidate_entry_cache(entry);} static void fuse_lookup_init(struct fuse_args *args, u64 nodeid,			     const struct qstr *name,			     struct fuse_entry_out *outarg){	memset(outarg, 0, sizeof(struct fuse_entry_out));	args->opcode = FUSE_LOOKUP;	args->nodeid = nodeid;	args->in_numargs = 3;	fuse_set_zero_arg0(args);	args->in_args[1].size = name->len;	args->in_args[1].value = name->name;	args->in_args[2].size = 1;	args->in_args[2].value = "";	args->out_numargs = 1;	args->out_args[0].size = sizeof(struct fuse_entry_out);	args->out_args[0].value = outarg;} /* * Check whether the dentry is still valid * * If the entry validity timeout has expired and the dentry is * positive, try to redo the lookup.  If the lookup results in a * different inode, then let the VFS invalidate the dentry and redo * the lookup once more.  If the lookup results in the same inode, * then refresh the attributes, timeouts and mark the dentry valid. */static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,				  struct dentry *entry, unsigned int flags){	struct inode *inode;	struct fuse_mount *fm;	struct fuse_conn *fc;	struct fuse_inode *fi;	struct fuse_dentry *fd = entry->d_fsdata;	int ret; 	fc = get_fuse_conn_super(dir->i_sb);	if (fd->epoch < atomic_read(&fc->epoch))		goto invalid; 	inode = d_inode_rcu(entry);	if (inode && fuse_is_bad(inode))		goto invalid;	else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) ||		 (flags & (LOOKUP_EXCL | LOOKUP_REVAL | LOOKUP_RENAME_TARGET))) {		struct fuse_entry_out outarg;		FUSE_ARGS(args);		struct fuse_forget_link *forget;		u64 attr_version; 		/* For negative dentries, always do a fresh lookup */		if (!inode)			goto invalid; 		ret = -ECHILD;		if (flags & LOOKUP_RCU)			goto out; 		fm = get_fuse_mount(inode); 		forget = fuse_alloc_forget();		ret = -ENOMEM;		if (!forget)			goto out; 		attr_version = fuse_get_attr_version(fm->fc); 		fuse_lookup_init(&args, get_node_id(dir), name, &outarg);		ret = fuse_simple_request(fm, &args);		/* Zero nodeid is same as -ENOENT */		if (!ret && !outarg.nodeid)			ret = -ENOENT;		if (!ret) {			fi = get_fuse_inode(inode);			if (outarg.nodeid != get_node_id(inode) ||			    (bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) {				fuse_chan_queue_forget(fm->fc->chan, forget,						  outarg.nodeid, 1);				goto invalid;			}			spin_lock(&fi->lock);			fi->nlookup++;			spin_unlock(&fi->lock);		}		kfree(forget);		if (ret == -ENOMEM || ret == -EINTR)			goto out;		if (ret || fuse_invalid_attr(&outarg.attr) ||		    fuse_stale_inode(inode, outarg.generation, &outarg.attr))			goto invalid; 		forget_all_cached_acls(inode);		fuse_change_attributes(inode, &outarg.attr, NULL,				       ATTR_TIMEOUT(&outarg),				       attr_version);		fuse_change_entry_timeout(entry, &outarg);	} else if (inode) {		fi = get_fuse_inode(inode);		if (flags & LOOKUP_RCU) {			if (test_bit(FUSE_I_INIT_RDPLUS, &fi->state))				return -ECHILD;		} else if (test_and_clear_bit(FUSE_I_INIT_RDPLUS, &fi->state)) {			fuse_advise_use_readdirplus(dir);		}	}	ret = 1;out:	return ret; invalid:	ret = 0;	goto out;} static int fuse_dentry_init(struct dentry *dentry){	struct fuse_dentry *fd; 	fd = kzalloc_obj(struct fuse_dentry,			 GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE);	if (!fd)		return -ENOMEM; 	fd->dentry = dentry;	RB_CLEAR_NODE(&fd->node);	dentry->d_fsdata = fd;	/*	 * Initialising epoch to '0' ensures the dentry is invalid	 * if compared to fc->epoch, which is initialized to '1'.	 */	fuse_dentry_set_epoch(dentry, 0); 	return 0;} static void fuse_dentry_release(struct dentry *dentry)

Lines 1–500 of 2508.