| /* | 
 |  * linux/fs/9p/trans_fd.c | 
 |  * | 
 |  * File Descriptor Transport Layer | 
 |  * | 
 |  *  Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net> | 
 |  *  Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com> | 
 |  * | 
 |  *  This program is free software; you can redistribute it and/or modify | 
 |  *  it under the terms of the GNU General Public License as published by | 
 |  *  the Free Software Foundation; either version 2 of the License, or | 
 |  *  (at your option) any later version. | 
 |  * | 
 |  *  This program is distributed in the hope that it will be useful, | 
 |  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  *  GNU General Public License for more details. | 
 |  * | 
 |  *  You should have received a copy of the GNU General Public License | 
 |  *  along with this program; if not, write to: | 
 |  *  Free Software Foundation | 
 |  *  51 Franklin Street, Fifth Floor | 
 |  *  Boston, MA  02111-1301  USA | 
 |  * | 
 |  */ | 
 |  | 
 | #include <linux/config.h> | 
 | #include <linux/module.h> | 
 | #include <linux/net.h> | 
 | #include <linux/ipv6.h> | 
 | #include <linux/errno.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/un.h> | 
 | #include <asm/uaccess.h> | 
 | #include <linux/inet.h> | 
 | #include <linux/idr.h> | 
 | #include <linux/file.h> | 
 |  | 
 | #include "debug.h" | 
 | #include "v9fs.h" | 
 | #include "transport.h" | 
 |  | 
 | struct v9fs_trans_fd { | 
 | 	struct file *in_file; | 
 | 	struct file *out_file; | 
 | }; | 
 |  | 
 | /** | 
 |  * v9fs_fd_recv - receive from a socket | 
 |  * @v9ses: session information | 
 |  * @v: buffer to receive data into | 
 |  * @len: size of receive buffer | 
 |  * | 
 |  */ | 
 |  | 
 | static int v9fs_fd_recv(struct v9fs_transport *trans, void *v, int len) | 
 | { | 
 | 	struct v9fs_trans_fd *ts = trans ? trans->priv : NULL; | 
 |  | 
 | 	if (!trans || trans->status != Connected || !ts) | 
 | 		return -EIO; | 
 |  | 
 | 	return kernel_read(ts->in_file, ts->in_file->f_pos, v, len); | 
 | } | 
 |  | 
 | /** | 
 |  * v9fs_fd_send - send to a socket | 
 |  * @v9ses: session information | 
 |  * @v: buffer to send data from | 
 |  * @len: size of send buffer | 
 |  * | 
 |  */ | 
 |  | 
 | static int v9fs_fd_send(struct v9fs_transport *trans, void *v, int len) | 
 | { | 
 | 	struct v9fs_trans_fd *ts = trans ? trans->priv : NULL; | 
 | 	mm_segment_t oldfs = get_fs(); | 
 | 	int ret = 0; | 
 |  | 
 | 	if (!trans || trans->status != Connected || !ts) | 
 | 		return -EIO; | 
 |  | 
 | 	set_fs(get_ds()); | 
 | 	/* The cast to a user pointer is valid due to the set_fs() */ | 
 | 	ret = vfs_write(ts->out_file, (void __user *)v, len, &ts->out_file->f_pos); | 
 | 	set_fs(oldfs); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | /** | 
 |  * v9fs_fd_init - initialize file descriptor transport | 
 |  * @v9ses: session information | 
 |  * @addr: address of server to mount | 
 |  * @data: mount options | 
 |  * | 
 |  */ | 
 |  | 
 | static int | 
 | v9fs_fd_init(struct v9fs_session_info *v9ses, const char *addr, char *data) | 
 | { | 
 | 	struct v9fs_trans_fd *ts = NULL; | 
 | 	struct v9fs_transport *trans = v9ses->transport; | 
 |  | 
 | 	if((v9ses->wfdno == ~0) || (v9ses->rfdno == ~0)) { | 
 | 		printk(KERN_ERR "v9fs: Insufficient options for proto=fd\n"); | 
 | 		return -ENOPROTOOPT; | 
 | 	} | 
 |  | 
 | 	ts = kmalloc(sizeof(struct v9fs_trans_fd), GFP_KERNEL); | 
 |  | 
 | 	if (!ts) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	ts->in_file = fget( v9ses->rfdno ); | 
 | 	ts->out_file = fget( v9ses->wfdno ); | 
 |  | 
 | 	if (!ts->in_file || !ts->out_file) { | 
 | 		if (ts->in_file) | 
 | 			fput(ts->in_file); | 
 |  | 
 | 		if (ts->out_file) | 
 | 			fput(ts->out_file); | 
 |  | 
 | 		kfree(ts); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	trans->priv = ts; | 
 | 	trans->status = Connected; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | /** | 
 |  * v9fs_fd_close - shutdown file descriptor | 
 |  * @trans: private socket structure | 
 |  * | 
 |  */ | 
 |  | 
 | static void v9fs_fd_close(struct v9fs_transport *trans) | 
 | { | 
 | 	struct v9fs_trans_fd *ts; | 
 |  | 
 | 	if (!trans) | 
 | 		return; | 
 |  | 
 | 	ts = xchg(&trans->priv, NULL); | 
 |  | 
 | 	if (!ts) | 
 | 		return; | 
 |  | 
 | 	trans->status = Disconnected; | 
 | 	if (ts->in_file) | 
 | 		fput(ts->in_file); | 
 |  | 
 | 	if (ts->out_file) | 
 | 		fput(ts->out_file); | 
 |  | 
 | 	kfree(ts); | 
 | } | 
 |  | 
 | static unsigned int | 
 | v9fs_fd_poll(struct v9fs_transport *trans, struct poll_table_struct *pt) | 
 | { | 
 | 	int ret, n; | 
 | 	struct v9fs_trans_fd *ts; | 
 | 	mm_segment_t oldfs; | 
 |  | 
 | 	if (!trans) | 
 | 		return -EIO; | 
 |  | 
 | 	ts = trans->priv; | 
 | 	if (trans->status != Connected || !ts) | 
 | 		return -EIO; | 
 |  | 
 | 	oldfs = get_fs(); | 
 | 	set_fs(get_ds()); | 
 |  | 
 | 	if (!ts->in_file->f_op || !ts->in_file->f_op->poll) { | 
 | 		ret = -EIO; | 
 | 		goto end; | 
 | 	} | 
 |  | 
 | 	ret = ts->in_file->f_op->poll(ts->in_file, pt); | 
 |  | 
 | 	if (ts->out_file != ts->in_file) { | 
 | 		if (!ts->out_file->f_op || !ts->out_file->f_op->poll) { | 
 | 			ret = -EIO; | 
 | 			goto end; | 
 | 		} | 
 |  | 
 | 		n = ts->out_file->f_op->poll(ts->out_file, pt); | 
 |  | 
 | 		ret &= ~POLLOUT; | 
 | 		n &= ~POLLIN; | 
 |  | 
 | 		ret |= n; | 
 | 	} | 
 |  | 
 | end: | 
 | 	set_fs(oldfs); | 
 | 	return ret; | 
 | } | 
 |  | 
 |  | 
 | struct v9fs_transport v9fs_trans_fd = { | 
 | 	.init = v9fs_fd_init, | 
 | 	.write = v9fs_fd_send, | 
 | 	.read = v9fs_fd_recv, | 
 | 	.close = v9fs_fd_close, | 
 | 	.poll = v9fs_fd_poll, | 
 | }; | 
 |  |