1935 lines · 46.7 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> static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx){	struct fuse_conn *fc = get_fuse_conn(dir);	struct fuse_inode *fi = get_fuse_inode(dir); 	if (!fc->do_readdirplus)		return false;	if (!fc->readdirplus_auto)		return true;	if (test_and_clear_bit(FUSE_I_ADVISE_RDPLUS, &fi->state))		return true;	if (ctx->pos == 0)		return true;	return false;} 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_time = time;} static inline u64 fuse_dentry_time(struct dentry *entry){	return entry->d_time;}#else/* * On 32 bit archs store the high 32 bits of time in d_fsdata */static void fuse_dentry_settime(struct dentry *entry, u64 time){	entry->d_time = time;	entry->d_fsdata = (void *) (unsigned long) (time >> 32);} static u64 fuse_dentry_time(struct dentry *entry){	return (u64) entry->d_time +		((u64) (unsigned long) entry->d_fsdata << 32);}#endif /* * FUSE caches dentries and attributes with separate timeout.  The * time in jiffies until the dentry/attributes are valid is stored in * dentry->d_time and fuse_inode->i_time respectively. */ /* * Calculate the time in jiffies until a dentry/attributes are valid */static u64 time_to_jiffies(unsigned long sec, unsigned long nsec){	if (sec || nsec) {		struct timespec ts = {sec, nsec};		return get_jiffies_64() + timespec_to_jiffies(&ts);	} else		return 0;} /* * Set dentry and possibly attribute timeouts from the lookup/mk* * replies */static 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);} static u64 entry_attr_timeout(struct fuse_entry_out *o){	return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);} /* * 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){	get_fuse_inode(inode)->i_time = 0;} /* * 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_req *req,			     u64 nodeid, struct qstr *name,			     struct fuse_entry_out *outarg){	memset(outarg, 0, sizeof(struct fuse_entry_out));	req->in.h.opcode = FUSE_LOOKUP;	req->in.h.nodeid = nodeid;	req->in.numargs = 1;	req->in.args[0].size = name->len + 1;	req->in.args[0].value = name->name;	req->out.numargs = 1;	if (fc->minor < 9)		req->out.args[0].size = FUSE_COMPAT_ENTRY_OUT_SIZE;	else		req->out.args[0].size = sizeof(struct fuse_entry_out);	req->out.args[0].value = outarg;} u64 fuse_get_attr_version(struct fuse_conn *fc){	u64 curr_version; 	/*	 * The spin lock isn't actually needed on 64bit archs, but we	 * don't yet care too much about such optimizations.	 */	spin_lock(&fc->lock);	curr_version = fc->attr_version;	spin_unlock(&fc->lock); 	return curr_version;} /* * 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 = ACCESS_ONCE(entry->d_inode);	if (inode && is_bad_inode(inode))		goto invalid;	else if (fuse_dentry_time(entry) < get_jiffies_64()) {		int err;		struct fuse_entry_out outarg;		struct fuse_req *req;		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);		req = fuse_get_req_nopages(fc);		ret = PTR_ERR(req);		if (IS_ERR(req))			goto out; 		forget = fuse_alloc_forget();		if (!forget) {			fuse_put_request(fc, req);			ret = -ENOMEM;			goto out;		} 		attr_version = fuse_get_attr_version(fc); 		parent = dget_parent(entry);		fuse_lookup_init(fc, req, get_node_id(parent->d_inode),				 &entry->d_name, &outarg);		fuse_request_send(fc, req);		dput(parent);		err = req->out.h.error;		fuse_put_request(fc, req);		/* Zero nodeid is same as -ENOENT */		if (!err && !outarg.nodeid)			err = -ENOENT;		if (!err) {			fi = get_fuse_inode(inode);			if (outarg.nodeid != get_node_id(inode)) {				fuse_queue_forget(fc, forget, outarg.nodeid, 1);				goto invalid;			}			spin_lock(&fc->lock);			fi->nlookup++;			spin_unlock(&fc->lock);		}		kfree(forget);		if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT)			goto invalid; 		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(parent->d_inode);			dput(parent);		}	}	ret = 1;out:	return ret; invalid:	ret = 0; 	if (!(flags & LOOKUP_RCU) && check_submounts_and_drop(entry) != 0)		ret = 1;	goto out;} static int invalid_nodeid(u64 nodeid){	return !nodeid || nodeid == FUSE_ROOT_ID;} const struct dentry_operations fuse_dentry_operations = {	.d_revalidate	= fuse_dentry_revalidate,}; 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);} int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct qstr *name,		     struct fuse_entry_out *outarg, struct inode **inode){	struct fuse_conn *fc = get_fuse_conn_super(sb);	struct fuse_req *req;	struct fuse_forget_link *forget;	u64 attr_version;	int err; 	*inode = NULL;	err = -ENAMETOOLONG;	if (name->len > FUSE_NAME_MAX)		goto out; 	req = fuse_get_req_nopages(fc);	err = PTR_ERR(req);	if (IS_ERR(req))		goto out; 	forget = fuse_alloc_forget();	err = -ENOMEM;	if (!forget) {		fuse_put_request(fc, req);		goto out;	} 	attr_version = fuse_get_attr_version(fc); 	fuse_lookup_init(fc, req, nodeid, name, outarg);	fuse_request_send(fc, req);	err = req->out.h.error;	fuse_put_request(fc, req);	/* 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_valid_type(outarg->attr.mode))		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; 	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,			       &outarg, &inode);	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_materialise_unique(entry, inode);	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); 	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 *opened){	int err;	struct inode *inode;	struct fuse_conn *fc = get_fuse_conn(dir);	struct fuse_req *req;	struct fuse_forget_link *forget;	struct fuse_create_in inarg;	struct fuse_open_out outopen;	struct fuse_entry_out outentry;	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; 	req = fuse_get_req_nopages(fc);	err = PTR_ERR(req);	if (IS_ERR(req))		goto out_put_forget_req; 	err = -ENOMEM;	ff = fuse_file_alloc(fc);	if (!ff)		goto out_put_request; 	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();	req->in.h.opcode = FUSE_CREATE;	req->in.h.nodeid = get_node_id(dir);	req->in.numargs = 2;	req->in.args[0].size = fc->minor < 12 ? sizeof(struct fuse_open_in) :						sizeof(inarg);	req->in.args[0].value = &inarg;	req->in.args[1].size = entry->d_name.len + 1;	req->in.args[1].value = entry->d_name.name;	req->out.numargs = 2;	if (fc->minor < 9)		req->out.args[0].size = FUSE_COMPAT_ENTRY_OUT_SIZE;	else		req->out.args[0].size = sizeof(outentry);	req->out.args[0].value = &outentry;	req->out.args[1].size = sizeof(outopen);	req->out.args[1].value = &outopen;	fuse_request_send(fc, req);	err = req->out.h.error;	if (err)		goto out_free_ff; 	err = -EIO;	if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid))		goto out_free_ff; 	fuse_put_request(fc, req);	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(ff, flags);		fuse_queue_forget(fc, forget, outentry.nodeid, 1);		err = -ENOMEM;		goto out_err;	}	kfree(forget);	d_instantiate(entry, inode);	fuse_change_entry_timeout(entry, &outentry);	fuse_invalidate_attr(dir);	err = finish_open(file, entry, generic_file_open, opened);	if (err) {		fuse_sync_release(ff, flags);	} else {		file->private_data = fuse_file_get(ff);		fuse_finish_open(inode, file);	}	return err; out_free_ff:	fuse_file_free(ff);out_put_request:	fuse_put_request(fc, req);out_put_forget_req:	kfree(forget);out_err:	return err;} static int fuse_mknod(struct inode *, struct dentry *, umode_t, dev_t);static int fuse_atomic_open(struct inode *dir, struct dentry *entry,			    struct file *file, unsigned flags,			    umode_t mode, int *opened){	int err;

Lines 1–500 of 1935.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t