1/*
2 * linux/fs/9p/trans_fd.c
3 *
4 * Fd transport layer.  Includes deprecated socket layer.
5 *
6 *  Copyright (C) 2006 by Russ Cox <rsc@swtch.com>
7 *  Copyright (C) 2004-2005 by Latchesar Ionkov <lucho@ionkov.net>
8 *  Copyright (C) 2004-2005 by Eric Van Hensbergen <ericvh@gmail.com>
9 *  Copyright (C) 1997-2002 by Ron Minnich <rminnich@sarnoff.com>
10 *
11 *  This program is free software; you can redistribute it and/or modify
12 *  it under the terms of the GNU General Public License version 2
13 *  as published by the Free Software Foundation.
14 *
15 *  This program is distributed in the hope that it will be useful,
16 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 *  GNU General Public License for more details.
19 *
20 *  You should have received a copy of the GNU General Public License
21 *  along with this program; if not, write to:
22 *  Free Software Foundation
23 *  51 Franklin Street, Fifth Floor
24 *  Boston, MA  02111-1301  USA
25 *
26 */
27
28#include <linux/in.h>
29#include <linux/module.h>
30#include <linux/net.h>
31#include <linux/ipv6.h>
32#include <linux/errno.h>
33#include <linux/kernel.h>
34#include <linux/un.h>
35#include <asm/uaccess.h>
36#include <linux/inet.h>
37#include <linux/idr.h>
38#include <linux/file.h>
39
40#include "debug.h"
41#include "v9fs.h"
42#include "transport.h"
43
44#define V9FS_PORT 564
45
46struct v9fs_trans_fd {
47	struct file *rd;
48	struct file *wr;
49};
50
51/**
52 * v9fs_fd_read- read from a fd
53 * @v9ses: session information
54 * @v: buffer to receive data into
55 * @len: size of receive buffer
56 *
57 */
58static int v9fs_fd_read(struct v9fs_transport *trans, void *v, int len)
59{
60	int ret;
61	struct v9fs_trans_fd *ts;
62
63	if (!trans || trans->status == Disconnected || !(ts = trans->priv))
64		return -EREMOTEIO;
65
66	if (!(ts->rd->f_flags & O_NONBLOCK))
67		dprintk(DEBUG_ERROR, "blocking read ...\n");
68
69	ret = kernel_read(ts->rd, ts->rd->f_pos, v, len);
70	if (ret <= 0 && ret != -ERESTARTSYS && ret != -EAGAIN)
71		trans->status = Disconnected;
72	return ret;
73}
74
75/**
76 * v9fs_fd_write - write to a socket
77 * @v9ses: session information
78 * @v: buffer to send data from
79 * @len: size of send buffer
80 *
81 */
82static int v9fs_fd_write(struct v9fs_transport *trans, void *v, int len)
83{
84	int ret;
85	mm_segment_t oldfs;
86	struct v9fs_trans_fd *ts;
87
88	if (!trans || trans->status == Disconnected || !(ts = trans->priv))
89		return -EREMOTEIO;
90
91	if (!(ts->wr->f_flags & O_NONBLOCK))
92		dprintk(DEBUG_ERROR, "blocking write ...\n");
93
94	oldfs = get_fs();
95	set_fs(get_ds());
96	/* The cast to a user pointer is valid due to the set_fs() */
97	ret = vfs_write(ts->wr, (void __user *)v, len, &ts->wr->f_pos);
98	set_fs(oldfs);
99
100	if (ret <= 0 && ret != -ERESTARTSYS && ret != -EAGAIN)
101		trans->status = Disconnected;
102	return ret;
103}
104
105static unsigned int
106v9fs_fd_poll(struct v9fs_transport *trans, struct poll_table_struct *pt)
107{
108	int ret, n;
109	struct v9fs_trans_fd *ts;
110	mm_segment_t oldfs;
111
112	if (!trans || trans->status != Connected || !(ts = trans->priv))
113		return -EREMOTEIO;
114
115	if (!ts->rd->f_op || !ts->rd->f_op->poll)
116		return -EIO;
117
118	if (!ts->wr->f_op || !ts->wr->f_op->poll)
119		return -EIO;
120
121	oldfs = get_fs();
122	set_fs(get_ds());
123
124	ret = ts->rd->f_op->poll(ts->rd, pt);
125	if (ret < 0)
126		goto end;
127
128	if (ts->rd != ts->wr) {
129		n = ts->wr->f_op->poll(ts->wr, pt);
130		if (n < 0) {
131			ret = n;
132			goto end;
133		}
134		ret = (ret & ~POLLOUT) | (n & ~POLLIN);
135	}
136
137      end:
138	set_fs(oldfs);
139	return ret;
140}
141
142static int v9fs_fd_open(struct v9fs_session_info *v9ses, int rfd, int wfd)
143{
144	struct v9fs_transport *trans = v9ses->transport;
145	struct v9fs_trans_fd *ts = kmalloc(sizeof(struct v9fs_trans_fd),
146					   GFP_KERNEL);
147	if (!ts)
148		return -ENOMEM;
149
150	ts->rd = fget(rfd);
151	ts->wr = fget(wfd);
152	if (!ts->rd || !ts->wr) {
153		if (ts->rd)
154			fput(ts->rd);
155		if (ts->wr)
156			fput(ts->wr);
157		kfree(ts);
158		return -EIO;
159	}
160
161	trans->priv = ts;
162	trans->status = Connected;
163
164	return 0;
165}
166
167static int v9fs_fd_init(struct v9fs_session_info *v9ses, const char *addr,
168			char *data)
169{
170	if (v9ses->rfdno == ~0 || v9ses->wfdno == ~0) {
171		printk(KERN_ERR "v9fs: Insufficient options for proto=fd\n");
172		return -ENOPROTOOPT;
173	}
174
175	return v9fs_fd_open(v9ses, v9ses->rfdno, v9ses->wfdno);
176}
177
178static int v9fs_socket_open(struct v9fs_session_info *v9ses,
179			    struct socket *csocket)
180{
181	int fd, ret;
182
183	csocket->sk->sk_allocation = GFP_NOIO;
184	if ((fd = sock_map_fd(csocket)) < 0) {
185		eprintk(KERN_ERR, "v9fs_socket_open: failed to map fd\n");
186		ret = fd;
187	      release_csocket:
188		sock_release(csocket);
189		return ret;
190	}
191
192	if ((ret = v9fs_fd_open(v9ses, fd, fd)) < 0) {
193		sockfd_put(csocket);
194		eprintk(KERN_ERR, "v9fs_socket_open: failed to open fd\n");
195		goto release_csocket;
196	}
197
198	((struct v9fs_trans_fd *)v9ses->transport->priv)->rd->f_flags |=
199	    O_NONBLOCK;
200	return 0;
201}
202
203static int v9fs_tcp_init(struct v9fs_session_info *v9ses, const char *addr,
204			 char *data)
205{
206	int ret;
207	struct socket *csocket = NULL;
208	struct sockaddr_in sin_server;
209
210	sin_server.sin_family = AF_INET;
211	sin_server.sin_addr.s_addr = in_aton(addr);
212	sin_server.sin_port = htons(v9ses->port);
213	sock_create_kern(PF_INET, SOCK_STREAM, IPPROTO_TCP, &csocket);
214
215	if (!csocket) {
216		eprintk(KERN_ERR, "v9fs_trans_tcp: problem creating socket\n");
217		return -1;
218	}
219
220	ret = csocket->ops->connect(csocket,
221				    (struct sockaddr *)&sin_server,
222				    sizeof(struct sockaddr_in), 0);
223	if (ret < 0) {
224		eprintk(KERN_ERR,
225			"v9fs_trans_tcp: problem connecting socket to %s\n",
226			addr);
227		return ret;
228	}
229
230	return v9fs_socket_open(v9ses, csocket);
231}
232
233static int
234v9fs_unix_init(struct v9fs_session_info *v9ses, const char *addr, char *data)
235{
236	int ret;
237	struct socket *csocket;
238	struct sockaddr_un sun_server;
239
240	if (strlen(addr) > UNIX_PATH_MAX) {
241		eprintk(KERN_ERR, "v9fs_trans_unix: address too long: %s\n",
242			addr);
243		return -ENAMETOOLONG;
244	}
245
246	sun_server.sun_family = PF_UNIX;
247	strcpy(sun_server.sun_path, addr);
248	sock_create_kern(PF_UNIX, SOCK_STREAM, 0, &csocket);
249	ret = csocket->ops->connect(csocket, (struct sockaddr *)&sun_server,
250			sizeof(struct sockaddr_un) - 1, 0);
251	if (ret < 0) {
252		eprintk(KERN_ERR,
253			"v9fs_trans_unix: problem connecting socket: %s: %d\n",
254			addr, ret);
255		return ret;
256	}
257
258	return v9fs_socket_open(v9ses, csocket);
259}
260
261/**
262 * v9fs_sock_close - shutdown socket
263 * @trans: private socket structure
264 *
265 */
266static void v9fs_fd_close(struct v9fs_transport *trans)
267{
268	struct v9fs_trans_fd *ts;
269
270	if (!trans)
271		return;
272
273	ts = xchg(&trans->priv, NULL);
274
275	if (!ts)
276		return;
277
278	trans->status = Disconnected;
279	if (ts->rd)
280		fput(ts->rd);
281	if (ts->wr)
282		fput(ts->wr);
283	kfree(ts);
284}
285
286struct v9fs_transport v9fs_trans_fd = {
287	.init = v9fs_fd_init,
288	.write = v9fs_fd_write,
289	.read = v9fs_fd_read,
290	.close = v9fs_fd_close,
291	.poll = v9fs_fd_poll,
292};
293
294struct v9fs_transport v9fs_trans_tcp = {
295	.init = v9fs_tcp_init,
296	.write = v9fs_fd_write,
297	.read = v9fs_fd_read,
298	.close = v9fs_fd_close,
299	.poll = v9fs_fd_poll,
300};
301
302struct v9fs_transport v9fs_trans_unix = {
303	.init = v9fs_unix_init,
304	.write = v9fs_fd_write,
305	.read = v9fs_fd_read,
306	.close = v9fs_fd_close,
307	.poll = v9fs_fd_poll,
308};
309