Pull bugzilla-5737 into release branch
[linux-2.6] / fs / 9p / trans_fd.c
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/config.h>
29 #include <linux/in.h>
30 #include <linux/module.h>
31 #include <linux/net.h>
32 #include <linux/ipv6.h>
33 #include <linux/errno.h>
34 #include <linux/kernel.h>
35 #include <linux/un.h>
36 #include <asm/uaccess.h>
37 #include <linux/inet.h>
38 #include <linux/idr.h>
39 #include <linux/file.h>
40
41 #include "debug.h"
42 #include "v9fs.h"
43 #include "transport.h"
44
45 #define V9FS_PORT 564
46
47 struct v9fs_trans_fd {
48         struct file *rd;
49         struct file *wr;
50 };
51
52 /**
53  * v9fs_fd_read- read from a fd
54  * @v9ses: session information
55  * @v: buffer to receive data into
56  * @len: size of receive buffer
57  *
58  */
59 static int v9fs_fd_read(struct v9fs_transport *trans, void *v, int len)
60 {
61         int ret;
62         struct v9fs_trans_fd *ts;
63
64         if (!trans || trans->status == Disconnected || !(ts = trans->priv))
65                 return -EREMOTEIO;
66
67         if (!(ts->rd->f_flags & O_NONBLOCK))
68                 dprintk(DEBUG_ERROR, "blocking read ...\n");
69
70         ret = kernel_read(ts->rd, ts->rd->f_pos, v, len);
71         if (ret <= 0 && ret != -ERESTARTSYS && ret != -EAGAIN)
72                 trans->status = Disconnected;
73         return ret;
74 }
75
76 /**
77  * v9fs_fd_write - write to a socket
78  * @v9ses: session information
79  * @v: buffer to send data from
80  * @len: size of send buffer
81  *
82  */
83 static int v9fs_fd_write(struct v9fs_transport *trans, void *v, int len)
84 {
85         int ret;
86         mm_segment_t oldfs;
87         struct v9fs_trans_fd *ts;
88
89         if (!trans || trans->status == Disconnected || !(ts = trans->priv))
90                 return -EREMOTEIO;
91
92         if (!(ts->wr->f_flags & O_NONBLOCK))
93                 dprintk(DEBUG_ERROR, "blocking write ...\n");
94
95         oldfs = get_fs();
96         set_fs(get_ds());
97         /* The cast to a user pointer is valid due to the set_fs() */
98         ret = vfs_write(ts->wr, (void __user *)v, len, &ts->wr->f_pos);
99         set_fs(oldfs);
100
101         if (ret <= 0 && ret != -ERESTARTSYS && ret != -EAGAIN)
102                 trans->status = Disconnected;
103         return ret;
104 }
105
106 static unsigned int
107 v9fs_fd_poll(struct v9fs_transport *trans, struct poll_table_struct *pt)
108 {
109         int ret, n;
110         struct v9fs_trans_fd *ts;
111         mm_segment_t oldfs;
112
113         if (!trans || trans->status != Connected || !(ts = trans->priv))
114                 return -EREMOTEIO;
115
116         if (!ts->rd->f_op || !ts->rd->f_op->poll)
117                 return -EIO;
118
119         if (!ts->wr->f_op || !ts->wr->f_op->poll)
120                 return -EIO;
121
122         oldfs = get_fs();
123         set_fs(get_ds());
124
125         ret = ts->rd->f_op->poll(ts->rd, pt);
126         if (ret < 0)
127                 goto end;
128
129         if (ts->rd != ts->wr) {
130                 n = ts->wr->f_op->poll(ts->wr, pt);
131                 if (n < 0) {
132                         ret = n;
133                         goto end;
134                 }
135                 ret = (ret & ~POLLOUT) | (n & ~POLLIN);
136         }
137
138       end:
139         set_fs(oldfs);
140         return ret;
141 }
142
143 static int v9fs_fd_open(struct v9fs_session_info *v9ses, int rfd, int wfd)
144 {
145         struct v9fs_transport *trans = v9ses->transport;
146         struct v9fs_trans_fd *ts = kmalloc(sizeof(struct v9fs_trans_fd),
147                                            GFP_KERNEL);
148         if (!ts)
149                 return -ENOMEM;
150
151         ts->rd = fget(rfd);
152         ts->wr = fget(wfd);
153         if (!ts->rd || !ts->wr) {
154                 if (ts->rd)
155                         fput(ts->rd);
156                 if (ts->wr)
157                         fput(ts->wr);
158                 kfree(ts);
159                 return -EIO;
160         }
161
162         trans->priv = ts;
163         trans->status = Connected;
164
165         return 0;
166 }
167
168 static int v9fs_fd_init(struct v9fs_session_info *v9ses, const char *addr,
169                         char *data)
170 {
171         if (v9ses->rfdno == ~0 || v9ses->wfdno == ~0) {
172                 printk(KERN_ERR "v9fs: Insufficient options for proto=fd\n");
173                 return -ENOPROTOOPT;
174         }
175
176         return v9fs_fd_open(v9ses, v9ses->rfdno, v9ses->wfdno);
177 }
178
179 static int v9fs_socket_open(struct v9fs_session_info *v9ses,
180                             struct socket *csocket)
181 {
182         int fd, ret;
183
184         csocket->sk->sk_allocation = GFP_NOIO;
185         if ((fd = sock_map_fd(csocket)) < 0) {
186                 eprintk(KERN_ERR, "v9fs_socket_open: failed to map fd\n");
187                 ret = fd;
188               release_csocket:
189                 sock_release(csocket);
190                 return ret;
191         }
192
193         if ((ret = v9fs_fd_open(v9ses, fd, fd)) < 0) {
194                 sockfd_put(csocket);
195                 eprintk(KERN_ERR, "v9fs_socket_open: failed to open fd\n");
196                 goto release_csocket;
197         }
198
199         ((struct v9fs_trans_fd *)v9ses->transport->priv)->rd->f_flags |=
200             O_NONBLOCK;
201         return 0;
202 }
203
204 static int v9fs_tcp_init(struct v9fs_session_info *v9ses, const char *addr,
205                          char *data)
206 {
207         int ret;
208         struct socket *csocket = NULL;
209         struct sockaddr_in sin_server;
210
211         sin_server.sin_family = AF_INET;
212         sin_server.sin_addr.s_addr = in_aton(addr);
213         sin_server.sin_port = htons(v9ses->port);
214         sock_create_kern(PF_INET, SOCK_STREAM, IPPROTO_TCP, &csocket);
215
216         if (!csocket) {
217                 eprintk(KERN_ERR, "v9fs_trans_tcp: problem creating socket\n");
218                 return -1;
219         }
220
221         ret = csocket->ops->connect(csocket,
222                                     (struct sockaddr *)&sin_server,
223                                     sizeof(struct sockaddr_in), 0);
224         if (ret < 0) {
225                 eprintk(KERN_ERR,
226                         "v9fs_trans_tcp: problem connecting socket to %s\n",
227                         addr);
228                 return ret;
229         }
230
231         return v9fs_socket_open(v9ses, csocket);
232 }
233
234 static int
235 v9fs_unix_init(struct v9fs_session_info *v9ses, const char *addr, char *data)
236 {
237         int ret;
238         struct socket *csocket;
239         struct sockaddr_un sun_server;
240
241         if (strlen(addr) > UNIX_PATH_MAX) {
242                 eprintk(KERN_ERR, "v9fs_trans_unix: address too long: %s\n",
243                         addr);
244                 return -ENAMETOOLONG;
245         }
246
247         sun_server.sun_family = PF_UNIX;
248         strcpy(sun_server.sun_path, addr);
249         sock_create_kern(PF_UNIX, SOCK_STREAM, 0, &csocket);
250         ret = csocket->ops->connect(csocket, (struct sockaddr *)&sun_server,
251                         sizeof(struct sockaddr_un) - 1, 0);
252         if (ret < 0) {
253                 eprintk(KERN_ERR,
254                         "v9fs_trans_unix: problem connecting socket: %s: %d\n",
255                         addr, ret);
256                 return ret;
257         }
258
259         return v9fs_socket_open(v9ses, csocket);
260 }
261
262 /**
263  * v9fs_sock_close - shutdown socket
264  * @trans: private socket structure
265  *
266  */
267 static void v9fs_fd_close(struct v9fs_transport *trans)
268 {
269         struct v9fs_trans_fd *ts;
270
271         if (!trans)
272                 return;
273
274         ts = xchg(&trans->priv, NULL);
275
276         if (!ts)
277                 return;
278
279         trans->status = Disconnected;
280         if (ts->rd)
281                 fput(ts->rd);
282         if (ts->wr)
283                 fput(ts->wr);
284         kfree(ts);
285 }
286
287 struct v9fs_transport v9fs_trans_fd = {
288         .init = v9fs_fd_init,
289         .write = v9fs_fd_write,
290         .read = v9fs_fd_read,
291         .close = v9fs_fd_close,
292         .poll = v9fs_fd_poll,
293 };
294
295 struct v9fs_transport v9fs_trans_tcp = {
296         .init = v9fs_tcp_init,
297         .write = v9fs_fd_write,
298         .read = v9fs_fd_read,
299         .close = v9fs_fd_close,
300         .poll = v9fs_fd_poll,
301 };
302
303 struct v9fs_transport v9fs_trans_unix = {
304         .init = v9fs_unix_init,
305         .write = v9fs_fd_write,
306         .read = v9fs_fd_read,
307         .close = v9fs_fd_close,
308         .poll = v9fs_fd_poll,
309 };