1216 lines · 31.0 KB · C
RawDownload
/*  FUSE: Filesystem in Userspace  Copyright (C) 2001-2005  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/gfp.h>#include <linux/sched.h>#include <linux/namei.h> /* * 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 unsigned long time_to_jiffies(unsigned long sec, unsigned long nsec){	struct timespec ts = {sec, nsec};	return jiffies + timespec_to_jiffies(&ts);} /* * Set dentry and possibly attribute timeouts from the lookup/mk* * replies */static void fuse_change_timeout(struct dentry *entry, struct fuse_entry_out *o){	entry->d_time = time_to_jiffies(o->entry_valid, o->entry_valid_nsec);	if (entry->d_inode)		get_fuse_inode(entry->d_inode)->i_time =			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 = jiffies - 1;} /* * 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) */static void fuse_invalidate_entry_cache(struct dentry *entry){	entry->d_time = jiffies - 1;} /* * 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_req *req, struct inode *dir,			     struct dentry *entry,			     struct fuse_entry_out *outarg){	req->in.h.opcode = FUSE_LOOKUP;	req->in.h.nodeid = get_node_id(dir);	req->inode = dir;	req->in.numargs = 1;	req->in.args[0].size = entry->d_name.len + 1;	req->in.args[0].value = entry->d_name.name;	req->out.numargs = 1;	req->out.args[0].size = sizeof(struct fuse_entry_out);	req->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, struct nameidata *nd){	struct inode *inode = entry->d_inode; 	if (inode && is_bad_inode(inode))		return 0;	else if (time_after(jiffies, entry->d_time)) {		int err;		struct fuse_entry_out outarg;		struct fuse_conn *fc;		struct fuse_req *req; 		/* Doesn't hurt to "reset" the validity timeout */		fuse_invalidate_entry_cache(entry); 		/* For negative dentries, always do a fresh lookup */		if (!inode)			return 0; 		fc = get_fuse_conn(inode);		req = fuse_get_req(fc);		if (IS_ERR(req))			return 0; 		fuse_lookup_init(req, entry->d_parent->d_inode, entry, &outarg);		request_send(fc, req);		err = req->out.h.error;		/* Zero nodeid is same as -ENOENT */		if (!err && !outarg.nodeid)			err = -ENOENT;		if (!err) {			struct fuse_inode *fi = get_fuse_inode(inode);			if (outarg.nodeid != get_node_id(inode)) {				fuse_send_forget(fc, req, outarg.nodeid, 1);				return 0;			}			fi->nlookup ++;		}		fuse_put_request(fc, req);		if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT)			return 0; 		fuse_change_attributes(inode, &outarg.attr);		fuse_change_timeout(entry, &outarg);	}	return 1;} /* * Check if there's already a hashed alias of this directory inode. * If yes, then lookup and mkdir must not create a new alias. */static int dir_alias(struct inode *inode){	if (S_ISDIR(inode->i_mode)) {		struct dentry *alias = d_find_alias(inode);		if (alias) {			dput(alias);			return 1;		}	}	return 0;} static int invalid_nodeid(u64 nodeid){	return !nodeid || nodeid == FUSE_ROOT_ID;} static struct dentry_operations fuse_dentry_operations = {	.d_revalidate	= fuse_dentry_revalidate,}; static int valid_mode(int m){	return S_ISREG(m) || S_ISDIR(m) || S_ISLNK(m) || S_ISCHR(m) ||		S_ISBLK(m) || S_ISFIFO(m) || S_ISSOCK(m);} static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,				  struct nameidata *nd){	int err;	struct fuse_entry_out outarg;	struct inode *inode = NULL;	struct fuse_conn *fc = get_fuse_conn(dir);	struct fuse_req *req; 	if (entry->d_name.len > FUSE_NAME_MAX)		return ERR_PTR(-ENAMETOOLONG); 	req = fuse_get_req(fc);	if (IS_ERR(req))		return ERR_PTR(PTR_ERR(req)); 	fuse_lookup_init(req, dir, entry, &outarg);	request_send(fc, req);	err = req->out.h.error;	/* Zero nodeid is same as -ENOENT, but with valid timeout */	if (!err && outarg.nodeid &&	    (invalid_nodeid(outarg.nodeid) || !valid_mode(outarg.attr.mode)))		err = -EIO;	if (!err && outarg.nodeid) {		inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,				  &outarg.attr);		if (!inode) {			fuse_send_forget(fc, req, outarg.nodeid, 1);			return ERR_PTR(-ENOMEM);		}	}	fuse_put_request(fc, req);	if (err && err != -ENOENT)		return ERR_PTR(err); 	if (inode && dir_alias(inode)) {		iput(inode);		return ERR_PTR(-EIO);	}	d_add(entry, inode);	entry->d_op = &fuse_dentry_operations;	if (!err)		fuse_change_timeout(entry, &outarg);	else		fuse_invalidate_entry_cache(entry);	return NULL;} /* * 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, int mode,			    struct nameidata *nd){	int err;	struct inode *inode;	struct fuse_conn *fc = get_fuse_conn(dir);	struct fuse_req *req;	struct fuse_open_in inarg;	struct fuse_open_out outopen;	struct fuse_entry_out outentry;	struct fuse_file *ff;	struct file *file;	int flags = nd->intent.open.flags - 1; 	if (fc->no_create)		return -ENOSYS; 	req = fuse_get_req(fc);	if (IS_ERR(req))		return PTR_ERR(req); 	err = -ENOMEM;	ff = fuse_file_alloc();	if (!ff)		goto out_put_request; 	flags &= ~O_NOCTTY;	memset(&inarg, 0, sizeof(inarg));	inarg.flags = flags;	inarg.mode = mode;	req->in.h.opcode = FUSE_CREATE;	req->in.h.nodeid = get_node_id(dir);	req->inode = dir;	req->in.numargs = 2;	req->in.args[0].size = 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;	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;	request_send(fc, req);	err = req->out.h.error;	if (err) {		if (err == -ENOSYS)			fc->no_create = 1;		goto out_free_ff;	} 	err = -EIO;	if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid))		goto out_free_ff; 	inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation,			  &outentry.attr);	err = -ENOMEM;	if (!inode) {		flags &= ~(O_CREAT | O_EXCL | O_TRUNC);		ff->fh = outopen.fh;		/* Special release, with inode = NULL, this will		   trigger a 'forget' request when the release is		   complete */		fuse_send_release(fc, ff, outentry.nodeid, NULL, flags, 0);		goto out_put_request;	}	fuse_put_request(fc, req);	d_instantiate(entry, inode);	fuse_change_timeout(entry, &outentry);	file = lookup_instantiate_filp(nd, entry, generic_file_open);	if (IS_ERR(file)) {		ff->fh = outopen.fh;		fuse_send_release(fc, ff, outentry.nodeid, inode, flags, 0);		return PTR_ERR(file);	}	fuse_finish_open(inode, file, ff, &outopen);	return 0;  out_free_ff:	fuse_file_free(ff); out_put_request:	fuse_put_request(fc, req);	return err;} /* * Code shared between mknod, mkdir, symlink and link */static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req,			    struct inode *dir, struct dentry *entry,			    int mode){	struct fuse_entry_out outarg;	struct inode *inode;	int err; 	req->in.h.nodeid = get_node_id(dir);	req->inode = dir;	req->out.numargs = 1;	req->out.args[0].size = sizeof(outarg);	req->out.args[0].value = &outarg;	request_send(fc, req);	err = req->out.h.error;	if (err) {		fuse_put_request(fc, req);		return err;	}	err = -EIO;	if (invalid_nodeid(outarg.nodeid))		goto out_put_request; 	if ((outarg.attr.mode ^ mode) & S_IFMT)		goto out_put_request; 	inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,			  &outarg.attr);	if (!inode) {		fuse_send_forget(fc, req, outarg.nodeid, 1);		return -ENOMEM;	}	fuse_put_request(fc, req); 	if (dir_alias(inode)) {		iput(inode);		return -EIO;	} 	d_instantiate(entry, inode);	fuse_change_timeout(entry, &outarg);	fuse_invalidate_attr(dir);	return 0;  out_put_request:	fuse_put_request(fc, req);	return err;} static int fuse_mknod(struct inode *dir, struct dentry *entry, int mode,		      dev_t rdev){	struct fuse_mknod_in inarg;	struct fuse_conn *fc = get_fuse_conn(dir);	struct fuse_req *req = fuse_get_req(fc);	if (IS_ERR(req))		return PTR_ERR(req); 	memset(&inarg, 0, sizeof(inarg));	inarg.mode = mode;	inarg.rdev = new_encode_dev(rdev);	req->in.h.opcode = FUSE_MKNOD;	req->in.numargs = 2;	req->in.args[0].size = 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;	return create_new_entry(fc, req, dir, entry, mode);} static int fuse_create(struct inode *dir, struct dentry *entry, int mode,		       struct nameidata *nd){	if (nd && (nd->flags & LOOKUP_CREATE)) {		int err = fuse_create_open(dir, entry, mode, nd);		if (err != -ENOSYS)			return err;		/* Fall back on mknod */	}	return fuse_mknod(dir, entry, mode, 0);} static int fuse_mkdir(struct inode *dir, struct dentry *entry, int mode){	struct fuse_mkdir_in inarg;	struct fuse_conn *fc = get_fuse_conn(dir);	struct fuse_req *req = fuse_get_req(fc);	if (IS_ERR(req))		return PTR_ERR(req); 	memset(&inarg, 0, sizeof(inarg));	inarg.mode = mode;	req->in.h.opcode = FUSE_MKDIR;	req->in.numargs = 2;	req->in.args[0].size = 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;	return create_new_entry(fc, req, dir, entry, S_IFDIR);} static int fuse_symlink(struct inode *dir, struct dentry *entry,			const char *link){	struct fuse_conn *fc = get_fuse_conn(dir);	unsigned len = strlen(link) + 1;	struct fuse_req *req = fuse_get_req(fc);	if (IS_ERR(req))		return PTR_ERR(req); 	req->in.h.opcode = FUSE_SYMLINK;	req->in.numargs = 2;	req->in.args[0].size = entry->d_name.len + 1;	req->in.args[0].value = entry->d_name.name;	req->in.args[1].size = len;	req->in.args[1].value = link;	return create_new_entry(fc, req, dir, entry, S_IFLNK);} static int fuse_unlink(struct inode *dir, struct dentry *entry){	int err;	struct fuse_conn *fc = get_fuse_conn(dir);	struct fuse_req *req = fuse_get_req(fc);	if (IS_ERR(req))		return PTR_ERR(req); 	req->in.h.opcode = FUSE_UNLINK;	req->in.h.nodeid = get_node_id(dir);	req->inode = dir;	req->in.numargs = 1;	req->in.args[0].size = entry->d_name.len + 1;	req->in.args[0].value = entry->d_name.name;	request_send(fc, req);	err = req->out.h.error;	fuse_put_request(fc, req);	if (!err) {		struct inode *inode = entry->d_inode; 		/* Set nlink to zero so the inode can be cleared, if                   the inode does have more links this will be                   discovered at the next lookup/getattr */		inode->i_nlink = 0;		fuse_invalidate_attr(inode);		fuse_invalidate_attr(dir);		fuse_invalidate_entry_cache(entry);	} else if (err == -EINTR)		fuse_invalidate_entry(entry);	return err;} static int fuse_rmdir(struct inode *dir, struct dentry *entry){	int err;	struct fuse_conn *fc = get_fuse_conn(dir);	struct fuse_req *req = fuse_get_req(fc);	if (IS_ERR(req))		return PTR_ERR(req); 	req->in.h.opcode = FUSE_RMDIR;	req->in.h.nodeid = get_node_id(dir);	req->inode = dir;	req->in.numargs = 1;	req->in.args[0].size = entry->d_name.len + 1;	req->in.args[0].value = entry->d_name.name;	request_send(fc, req);	err = req->out.h.error;	fuse_put_request(fc, req);	if (!err) {		entry->d_inode->i_nlink = 0;		fuse_invalidate_attr(dir);		fuse_invalidate_entry_cache(entry);	} else if (err == -EINTR)		fuse_invalidate_entry(entry);	return err;} static int fuse_rename(struct inode *olddir, struct dentry *oldent,		       struct inode *newdir, struct dentry *newent)

Lines 1–500 of 1216.