diff options
Diffstat (limited to 'fs/smbfs/sock.c')
-rw-r--r-- | fs/smbfs/sock.c | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/fs/smbfs/sock.c b/fs/smbfs/sock.c new file mode 100644 index 000000000000..93f3cd22a2e9 --- /dev/null +++ b/fs/smbfs/sock.c | |||
@@ -0,0 +1,388 @@ | |||
1 | /* | ||
2 | * sock.c | ||
3 | * | ||
4 | * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke | ||
5 | * Copyright (C) 1997 by Volker Lendecke | ||
6 | * | ||
7 | * Please add a note about your changes to smbfs in the ChangeLog file. | ||
8 | */ | ||
9 | |||
10 | #include <linux/fs.h> | ||
11 | #include <linux/time.h> | ||
12 | #include <linux/errno.h> | ||
13 | #include <linux/socket.h> | ||
14 | #include <linux/fcntl.h> | ||
15 | #include <linux/file.h> | ||
16 | #include <linux/in.h> | ||
17 | #include <linux/net.h> | ||
18 | #include <linux/tcp.h> | ||
19 | #include <linux/mm.h> | ||
20 | #include <linux/netdevice.h> | ||
21 | #include <linux/smp_lock.h> | ||
22 | #include <linux/workqueue.h> | ||
23 | #include <net/scm.h> | ||
24 | #include <net/ip.h> | ||
25 | |||
26 | #include <linux/smb_fs.h> | ||
27 | #include <linux/smb.h> | ||
28 | #include <linux/smbno.h> | ||
29 | |||
30 | #include <asm/uaccess.h> | ||
31 | #include <asm/ioctls.h> | ||
32 | |||
33 | #include "smb_debug.h" | ||
34 | #include "proto.h" | ||
35 | #include "request.h" | ||
36 | |||
37 | |||
38 | static int | ||
39 | _recvfrom(struct socket *socket, unsigned char *ubuf, int size, unsigned flags) | ||
40 | { | ||
41 | struct kvec iov = {ubuf, size}; | ||
42 | struct msghdr msg = {.msg_flags = flags}; | ||
43 | msg.msg_flags |= MSG_DONTWAIT | MSG_NOSIGNAL; | ||
44 | return kernel_recvmsg(socket, &msg, &iov, 1, size, msg.msg_flags); | ||
45 | } | ||
46 | |||
47 | /* | ||
48 | * Return the server this socket belongs to | ||
49 | */ | ||
50 | static struct smb_sb_info * | ||
51 | server_from_socket(struct socket *socket) | ||
52 | { | ||
53 | return socket->sk->sk_user_data; | ||
54 | } | ||
55 | |||
56 | /* | ||
57 | * Called when there is data on the socket. | ||
58 | */ | ||
59 | void | ||
60 | smb_data_ready(struct sock *sk, int len) | ||
61 | { | ||
62 | struct smb_sb_info *server = server_from_socket(sk->sk_socket); | ||
63 | void (*data_ready)(struct sock *, int) = server->data_ready; | ||
64 | |||
65 | data_ready(sk, len); | ||
66 | VERBOSE("(%p, %d)\n", sk, len); | ||
67 | smbiod_wake_up(); | ||
68 | } | ||
69 | |||
70 | int | ||
71 | smb_valid_socket(struct inode * inode) | ||
72 | { | ||
73 | return (inode && S_ISSOCK(inode->i_mode) && | ||
74 | SOCKET_I(inode)->type == SOCK_STREAM); | ||
75 | } | ||
76 | |||
77 | static struct socket * | ||
78 | server_sock(struct smb_sb_info *server) | ||
79 | { | ||
80 | struct file *file; | ||
81 | |||
82 | if (server && (file = server->sock_file)) | ||
83 | { | ||
84 | #ifdef SMBFS_PARANOIA | ||
85 | if (!smb_valid_socket(file->f_dentry->d_inode)) | ||
86 | PARANOIA("bad socket!\n"); | ||
87 | #endif | ||
88 | return SOCKET_I(file->f_dentry->d_inode); | ||
89 | } | ||
90 | return NULL; | ||
91 | } | ||
92 | |||
93 | void | ||
94 | smb_close_socket(struct smb_sb_info *server) | ||
95 | { | ||
96 | struct file * file = server->sock_file; | ||
97 | |||
98 | if (file) { | ||
99 | struct socket *sock = server_sock(server); | ||
100 | |||
101 | VERBOSE("closing socket %p\n", sock); | ||
102 | sock->sk->sk_data_ready = server->data_ready; | ||
103 | server->sock_file = NULL; | ||
104 | fput(file); | ||
105 | } | ||
106 | } | ||
107 | |||
108 | static int | ||
109 | smb_get_length(struct socket *socket, unsigned char *header) | ||
110 | { | ||
111 | int result; | ||
112 | |||
113 | result = _recvfrom(socket, header, 4, MSG_PEEK); | ||
114 | if (result == -EAGAIN) | ||
115 | return -ENODATA; | ||
116 | if (result < 0) { | ||
117 | PARANOIA("recv error = %d\n", -result); | ||
118 | return result; | ||
119 | } | ||
120 | if (result < 4) | ||
121 | return -ENODATA; | ||
122 | |||
123 | switch (header[0]) { | ||
124 | case 0x00: | ||
125 | case 0x82: | ||
126 | break; | ||
127 | |||
128 | case 0x85: | ||
129 | DEBUG1("Got SESSION KEEP ALIVE\n"); | ||
130 | _recvfrom(socket, header, 4, 0); /* read away */ | ||
131 | return -ENODATA; | ||
132 | |||
133 | default: | ||
134 | PARANOIA("Invalid NBT packet, code=%x\n", header[0]); | ||
135 | return -EIO; | ||
136 | } | ||
137 | |||
138 | /* The length in the RFC NB header is the raw data length */ | ||
139 | return smb_len(header); | ||
140 | } | ||
141 | |||
142 | int | ||
143 | smb_recv_available(struct smb_sb_info *server) | ||
144 | { | ||
145 | mm_segment_t oldfs; | ||
146 | int avail, err; | ||
147 | struct socket *sock = server_sock(server); | ||
148 | |||
149 | oldfs = get_fs(); | ||
150 | set_fs(get_ds()); | ||
151 | err = sock->ops->ioctl(sock, SIOCINQ, (unsigned long) &avail); | ||
152 | set_fs(oldfs); | ||
153 | return (err >= 0) ? avail : err; | ||
154 | } | ||
155 | |||
156 | /* | ||
157 | * Adjust the kvec to move on 'n' bytes (from nfs/sunrpc) | ||
158 | */ | ||
159 | static int | ||
160 | smb_move_iov(struct kvec **data, size_t *num, struct kvec *vec, unsigned amount) | ||
161 | { | ||
162 | struct kvec *iv = *data; | ||
163 | int i; | ||
164 | int len; | ||
165 | |||
166 | /* | ||
167 | * Eat any sent kvecs | ||
168 | */ | ||
169 | while (iv->iov_len <= amount) { | ||
170 | amount -= iv->iov_len; | ||
171 | iv++; | ||
172 | (*num)--; | ||
173 | } | ||
174 | |||
175 | /* | ||
176 | * And chew down the partial one | ||
177 | */ | ||
178 | vec[0].iov_len = iv->iov_len-amount; | ||
179 | vec[0].iov_base =((unsigned char *)iv->iov_base)+amount; | ||
180 | iv++; | ||
181 | |||
182 | len = vec[0].iov_len; | ||
183 | |||
184 | /* | ||
185 | * And copy any others | ||
186 | */ | ||
187 | for (i = 1; i < *num; i++) { | ||
188 | vec[i] = *iv++; | ||
189 | len += vec[i].iov_len; | ||
190 | } | ||
191 | |||
192 | *data = vec; | ||
193 | return len; | ||
194 | } | ||
195 | |||
196 | /* | ||
197 | * smb_receive_header | ||
198 | * Only called by the smbiod thread. | ||
199 | */ | ||
200 | int | ||
201 | smb_receive_header(struct smb_sb_info *server) | ||
202 | { | ||
203 | struct socket *sock; | ||
204 | int result = 0; | ||
205 | unsigned char peek_buf[4]; | ||
206 | |||
207 | result = -EIO; | ||
208 | sock = server_sock(server); | ||
209 | if (!sock) | ||
210 | goto out; | ||
211 | if (sock->sk->sk_state != TCP_ESTABLISHED) | ||
212 | goto out; | ||
213 | |||
214 | if (!server->smb_read) { | ||
215 | result = smb_get_length(sock, peek_buf); | ||
216 | if (result < 0) { | ||
217 | if (result == -ENODATA) | ||
218 | result = 0; | ||
219 | goto out; | ||
220 | } | ||
221 | server->smb_len = result + 4; | ||
222 | |||
223 | if (server->smb_len < SMB_HEADER_LEN) { | ||
224 | PARANOIA("short packet: %d\n", result); | ||
225 | server->rstate = SMB_RECV_DROP; | ||
226 | result = -EIO; | ||
227 | goto out; | ||
228 | } | ||
229 | if (server->smb_len > SMB_MAX_PACKET_SIZE) { | ||
230 | PARANOIA("long packet: %d\n", result); | ||
231 | server->rstate = SMB_RECV_DROP; | ||
232 | result = -EIO; | ||
233 | goto out; | ||
234 | } | ||
235 | } | ||
236 | |||
237 | result = _recvfrom(sock, server->header + server->smb_read, | ||
238 | SMB_HEADER_LEN - server->smb_read, 0); | ||
239 | VERBOSE("_recvfrom: %d\n", result); | ||
240 | if (result < 0) { | ||
241 | VERBOSE("receive error: %d\n", result); | ||
242 | goto out; | ||
243 | } | ||
244 | server->smb_read += result; | ||
245 | |||
246 | if (server->smb_read == SMB_HEADER_LEN) | ||
247 | server->rstate = SMB_RECV_HCOMPLETE; | ||
248 | out: | ||
249 | return result; | ||
250 | } | ||
251 | |||
252 | static char drop_buffer[PAGE_SIZE]; | ||
253 | |||
254 | /* | ||
255 | * smb_receive_drop - read and throw away the data | ||
256 | * Only called by the smbiod thread. | ||
257 | * | ||
258 | * FIXME: we are in the kernel, could we just tell the socket that we want | ||
259 | * to drop stuff from the buffer? | ||
260 | */ | ||
261 | int | ||
262 | smb_receive_drop(struct smb_sb_info *server) | ||
263 | { | ||
264 | struct socket *sock; | ||
265 | unsigned int flags; | ||
266 | struct kvec iov; | ||
267 | struct msghdr msg; | ||
268 | int rlen = smb_len(server->header) - server->smb_read + 4; | ||
269 | int result = -EIO; | ||
270 | |||
271 | if (rlen > PAGE_SIZE) | ||
272 | rlen = PAGE_SIZE; | ||
273 | |||
274 | sock = server_sock(server); | ||
275 | if (!sock) | ||
276 | goto out; | ||
277 | if (sock->sk->sk_state != TCP_ESTABLISHED) | ||
278 | goto out; | ||
279 | |||
280 | flags = MSG_DONTWAIT | MSG_NOSIGNAL; | ||
281 | iov.iov_base = drop_buffer; | ||
282 | iov.iov_len = PAGE_SIZE; | ||
283 | msg.msg_flags = flags; | ||
284 | msg.msg_name = NULL; | ||
285 | msg.msg_namelen = 0; | ||
286 | msg.msg_control = NULL; | ||
287 | |||
288 | result = kernel_recvmsg(sock, &msg, &iov, 1, rlen, flags); | ||
289 | |||
290 | VERBOSE("read: %d\n", result); | ||
291 | if (result < 0) { | ||
292 | VERBOSE("receive error: %d\n", result); | ||
293 | goto out; | ||
294 | } | ||
295 | server->smb_read += result; | ||
296 | |||
297 | if (server->smb_read >= server->smb_len) | ||
298 | server->rstate = SMB_RECV_END; | ||
299 | |||
300 | out: | ||
301 | return result; | ||
302 | } | ||
303 | |||
304 | /* | ||
305 | * smb_receive | ||
306 | * Only called by the smbiod thread. | ||
307 | */ | ||
308 | int | ||
309 | smb_receive(struct smb_sb_info *server, struct smb_request *req) | ||
310 | { | ||
311 | struct socket *sock; | ||
312 | unsigned int flags; | ||
313 | struct kvec iov[4]; | ||
314 | struct kvec *p = req->rq_iov; | ||
315 | size_t num = req->rq_iovlen; | ||
316 | struct msghdr msg; | ||
317 | int rlen; | ||
318 | int result = -EIO; | ||
319 | |||
320 | sock = server_sock(server); | ||
321 | if (!sock) | ||
322 | goto out; | ||
323 | if (sock->sk->sk_state != TCP_ESTABLISHED) | ||
324 | goto out; | ||
325 | |||
326 | flags = MSG_DONTWAIT | MSG_NOSIGNAL; | ||
327 | msg.msg_flags = flags; | ||
328 | msg.msg_name = NULL; | ||
329 | msg.msg_namelen = 0; | ||
330 | msg.msg_control = NULL; | ||
331 | |||
332 | /* Dont repeat bytes and count available bufferspace */ | ||
333 | rlen = smb_move_iov(&p, &num, iov, req->rq_bytes_recvd); | ||
334 | if (req->rq_rlen < rlen) | ||
335 | rlen = req->rq_rlen; | ||
336 | |||
337 | result = kernel_recvmsg(sock, &msg, p, num, rlen, flags); | ||
338 | |||
339 | VERBOSE("read: %d\n", result); | ||
340 | if (result < 0) { | ||
341 | VERBOSE("receive error: %d\n", result); | ||
342 | goto out; | ||
343 | } | ||
344 | req->rq_bytes_recvd += result; | ||
345 | server->smb_read += result; | ||
346 | |||
347 | out: | ||
348 | return result; | ||
349 | } | ||
350 | |||
351 | /* | ||
352 | * Try to send a SMB request. This may return after sending only parts of the | ||
353 | * request. SMB_REQ_TRANSMITTED will be set if a request was fully sent. | ||
354 | * | ||
355 | * Parts of this was taken from xprt_sendmsg from net/sunrpc/xprt.c | ||
356 | */ | ||
357 | int | ||
358 | smb_send_request(struct smb_request *req) | ||
359 | { | ||
360 | struct smb_sb_info *server = req->rq_server; | ||
361 | struct socket *sock; | ||
362 | struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT}; | ||
363 | int slen = req->rq_slen - req->rq_bytes_sent; | ||
364 | int result = -EIO; | ||
365 | struct kvec iov[4]; | ||
366 | struct kvec *p = req->rq_iov; | ||
367 | size_t num = req->rq_iovlen; | ||
368 | |||
369 | sock = server_sock(server); | ||
370 | if (!sock) | ||
371 | goto out; | ||
372 | if (sock->sk->sk_state != TCP_ESTABLISHED) | ||
373 | goto out; | ||
374 | |||
375 | /* Dont repeat bytes */ | ||
376 | if (req->rq_bytes_sent) | ||
377 | smb_move_iov(&p, &num, iov, req->rq_bytes_sent); | ||
378 | |||
379 | result = kernel_sendmsg(sock, &msg, p, num, slen); | ||
380 | |||
381 | if (result >= 0) { | ||
382 | req->rq_bytes_sent += result; | ||
383 | if (req->rq_bytes_sent >= req->rq_slen) | ||
384 | req->rq_flags |= SMB_REQ_TRANSMITTED; | ||
385 | } | ||
386 | out: | ||
387 | return result; | ||
388 | } | ||