1794 lines · 44.0 KB · C
RawDownload
/*  FUSE: Filesystem in Userspace  Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>   This program can be distributed under the terms of the GNU GPL.  See the file COPYING.*/ #include "fuse_i.h" #include <linux/pagemap.h>#include <linux/file.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> 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);} #if BITS_PER_LONG >= 64static inline void __fuse_dentry_settime(struct dentry *entry, u64 time){	entry->d_fsdata = (void *) time;} static inline u64 fuse_dentry_time(const struct dentry *entry){	return (u64)entry->d_fsdata;} #elseunion fuse_dentry {	u64 time;	struct rcu_head rcu;}; static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time){	((union fuse_dentry *) dentry->d_fsdata)->time = time;} static inline u64 fuse_dentry_time(const struct dentry *entry){	return ((union fuse_dentry *) entry->d_fsdata)->time;}#endif 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 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 */static u64 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,		time_to_jiffies(o->entry_valid, o->entry_valid_nsec));} static u64 attr_timeout(struct fuse_attr_out *o){	return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);} u64 entry_attr_timeout(struct fuse_entry_out *o){	return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);} static 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(dir);	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_conn *fc, 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 = 1;	args->in_args[0].size = name->len + 1;	args->in_args[0].value = name->name;	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 dentry *entry, unsigned int flags){	struct inode *inode;	struct dentry *parent;	struct fuse_conn *fc;	struct fuse_inode *fi;	int ret; 	inode = d_inode_rcu(entry);	if (inode && is_bad_inode(inode))		goto invalid;	else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) ||		 (flags & LOOKUP_REVAL)) {		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; 		fc = get_fuse_conn(inode); 		forget = fuse_alloc_forget();		ret = -ENOMEM;		if (!forget)			goto out; 		attr_version = fuse_get_attr_version(fc); 		parent = dget_parent(entry);		fuse_lookup_init(fc, &args, get_node_id(d_inode(parent)),				 &entry->d_name, &outarg);		ret = fuse_simple_request(fc, &args);		dput(parent);		/* 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)) {				fuse_queue_forget(fc, forget, outarg.nodeid, 1);				goto invalid;			}			spin_lock(&fi->lock);			fi->nlookup++;			spin_unlock(&fi->lock);		}		kfree(forget);		if (ret == -ENOMEM)			goto out;		if (ret || fuse_invalid_attr(&outarg.attr) ||		    (outarg.attr.mode ^ inode->i_mode) & S_IFMT)			goto invalid; 		forget_all_cached_acls(inode);		fuse_change_attributes(inode, &outarg.attr,				       entry_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)) {			parent = dget_parent(entry);			fuse_advise_use_readdirplus(d_inode(parent));			dput(parent);		}	}	ret = 1;out:	return ret; invalid:	ret = 0;	goto out;} #if BITS_PER_LONG < 64static int fuse_dentry_init(struct dentry *dentry){	dentry->d_fsdata = kzalloc(sizeof(union fuse_dentry),				   GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE); 	return dentry->d_fsdata ? 0 : -ENOMEM;}static void fuse_dentry_release(struct dentry *dentry){	union fuse_dentry *fd = dentry->d_fsdata; 	kfree_rcu(fd, rcu);}#endif static int fuse_dentry_delete(const struct dentry *dentry){	return time_before64(fuse_dentry_time(dentry), get_jiffies_64());} const struct dentry_operations fuse_dentry_operations = {	.d_revalidate	= fuse_dentry_revalidate,	.d_delete	= fuse_dentry_delete,#if BITS_PER_LONG < 64	.d_init		= fuse_dentry_init,	.d_release	= fuse_dentry_release,#endif}; const struct dentry_operations fuse_root_dentry_operations = {#if BITS_PER_LONG < 64	.d_init		= fuse_dentry_init,	.d_release	= fuse_dentry_release,#endif}; int fuse_valid_type(int m){	return S_ISREG(m) || S_ISDIR(m) || S_ISLNK(m) || S_ISCHR(m) ||		S_ISBLK(m) || S_ISFIFO(m) || S_ISSOCK(m);} bool fuse_invalid_attr(struct fuse_attr *attr){	return !fuse_valid_type(attr->mode) ||		attr->size > LLONG_MAX;} int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,		     struct fuse_entry_out *outarg, struct inode **inode){	struct fuse_conn *fc = get_fuse_conn_super(sb);	FUSE_ARGS(args);	struct fuse_forget_link *forget;	u64 attr_version;	int err; 	*inode = NULL;	err = -ENAMETOOLONG;	if (name->len > FUSE_NAME_MAX)		goto out;  	forget = fuse_alloc_forget();	err = -ENOMEM;	if (!forget)		goto out; 	attr_version = fuse_get_attr_version(fc); 	fuse_lookup_init(fc, &args, nodeid, name, outarg);	err = fuse_simple_request(fc, &args);	/* Zero nodeid is same as -ENOENT, but with valid timeout */	if (err || !outarg->nodeid)		goto out_put_forget; 	err = -EIO;	if (!outarg->nodeid)		goto out_put_forget;	if (fuse_invalid_attr(&outarg->attr))		goto out_put_forget; 	*inode = fuse_iget(sb, outarg->nodeid, outarg->generation,			   &outarg->attr, entry_attr_timeout(outarg),			   attr_version);	err = -ENOMEM;	if (!*inode) {		fuse_queue_forget(fc, forget, outarg->nodeid, 1);		goto out;	}	err = 0;  out_put_forget:	kfree(forget); out:	return err;} static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,				  unsigned int flags){	int err;	struct fuse_entry_out outarg;	struct inode *inode;	struct dentry *newent;	bool outarg_valid = true;	bool locked; 	locked = fuse_lock_inode(dir);	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,			       &outarg, &inode);	fuse_unlock_inode(dir, locked);	if (err == -ENOENT) {		outarg_valid = false;		err = 0;	}	if (err)		goto out_err; 	err = -EIO;	if (inode && get_node_id(inode) == FUSE_ROOT_ID)		goto out_iput; 	newent = d_splice_alias(inode, entry);	err = PTR_ERR(newent);	if (IS_ERR(newent))		goto out_err; 	entry = newent ? newent : entry;	if (outarg_valid)		fuse_change_entry_timeout(entry, &outarg);	else		fuse_invalidate_entry_cache(entry); 	if (inode)		fuse_advise_use_readdirplus(dir);	return newent;  out_iput:	iput(inode); out_err:	return ERR_PTR(err);} /* * Atomic create+open operation * * If the filesystem doesn't support this, then fall back to separate * 'mknod' + 'open' requests. */static int fuse_create_open(struct inode *dir, struct dentry *entry,			    struct file *file, unsigned flags,			    umode_t mode){	int err;	struct inode *inode;	struct fuse_conn *fc = get_fuse_conn(dir);	FUSE_ARGS(args);	struct fuse_forget_link *forget;	struct fuse_create_in inarg;	struct fuse_open_out outopen;	struct fuse_entry_out outentry;	struct fuse_inode *fi;	struct fuse_file *ff; 	/* Userspace expects S_IFREG in create mode */	BUG_ON((mode & S_IFMT) != S_IFREG); 	forget = fuse_alloc_forget();	err = -ENOMEM;	if (!forget)		goto out_err; 	err = -ENOMEM;	ff = fuse_file_alloc(fc);	if (!ff)		goto out_put_forget_req; 	if (!fc->dont_mask)		mode &= ~current_umask(); 	flags &= ~O_NOCTTY;	memset(&inarg, 0, sizeof(inarg));	memset(&outentry, 0, sizeof(outentry));	inarg.flags = flags;	inarg.mode = mode;	inarg.umask = current_umask();	args.opcode = FUSE_CREATE;	args.nodeid = get_node_id(dir);	args.in_numargs = 2;	args.in_args[0].size = sizeof(inarg);	args.in_args[0].value = &inarg;	args.in_args[1].size = entry->d_name.len + 1;	args.in_args[1].value = entry->d_name.name;	args.out_numargs = 2;	args.out_args[0].size = sizeof(outentry);	args.out_args[0].value = &outentry;	args.out_args[1].size = sizeof(outopen);	args.out_args[1].value = &outopen;	err = fuse_simple_request(fc, &args);	if (err)		goto out_free_ff; 	err = -EIO;	if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid) ||	    fuse_invalid_attr(&outentry.attr))		goto out_free_ff; 	ff->fh = outopen.fh;	ff->nodeid = outentry.nodeid;	ff->open_flags = outopen.open_flags;	inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation,			  &outentry.attr, entry_attr_timeout(&outentry), 0);	if (!inode) {		flags &= ~(O_CREAT | O_EXCL | O_TRUNC);		fuse_sync_release(NULL, ff, flags);		fuse_queue_forget(fc, forget, outentry.nodeid, 1);		err = -ENOMEM;		goto out_err;	}

Lines 1–500 of 1794.