| /* |
| * sock.c |
| * |
| * Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke |
| * Copyright (C) 1997 by Volker Lendecke |
| * |
| * Please add a note about your changes to smbfs in the ChangeLog file. |
| */ |
| |
| #include <linux/fs.h> |
| #include <linux/time.h> |
| #include <linux/errno.h> |
| #include <linux/socket.h> |
| #include <linux/fcntl.h> |
| #include <linux/file.h> |
| #include <linux/in.h> |
| #include <linux/net.h> |
| #include <linux/mm.h> |
| #include <linux/netdevice.h> |
| #include <linux/workqueue.h> |
| #include <net/scm.h> |
| #include <net/tcp_states.h> |
| #include <net/ip.h> |
| |
| #include <linux/smb_fs.h> |
| #include <linux/smb.h> |
| #include <linux/smbno.h> |
| |
| #include <asm/uaccess.h> |
| #include <asm/ioctls.h> |
| |
| #include "smb_debug.h" |
| #include "proto.h" |
| #include "request.h" |
| |
| |
| static int |
| _recvfrom(struct socket *socket, unsigned char *ubuf, int size, unsigned flags) |
| { |
| struct kvec iov = {ubuf, size}; |
| struct msghdr msg = {.msg_flags = flags}; |
| msg.msg_flags |= MSG_DONTWAIT | MSG_NOSIGNAL; |
| return kernel_recvmsg(socket, &msg, &iov, 1, size, msg.msg_flags); |
| } |
| |
| /* |
| * Return the server this socket belongs to |
| */ |
| static struct smb_sb_info * |
| server_from_socket(struct socket *socket) |
| { |
| return socket->sk->sk_user_data; |
| } |
| |
| /* |
| * Called when there is data on the socket. |
| */ |
| void |
| smb_data_ready(struct sock *sk, int len) |
| { |
| struct smb_sb_info *server = server_from_socket(sk->sk_socket); |
| void (*data_ready)(struct sock *, int) = server->data_ready; |
| |
| data_ready(sk, len); |
| VERBOSE("(%p, %d)\n", sk, len); |
| smbiod_wake_up(); |
| } |
| |
| int |
| smb_valid_socket(struct inode * inode) |
| { |
| return (inode && S_ISSOCK(inode->i_mode) && |
| SOCKET_I(inode)->type == SOCK_STREAM); |
| } |
| |
| static struct socket * |
| server_sock(struct smb_sb_info *server) |
| { |
| struct file *file; |
| |
| if (server && (file = server->sock_file)) |
| { |
| #ifdef SMBFS_PARANOIA |
| if (!smb_valid_socket(file->f_path.dentry->d_inode)) |
| PARANOIA("bad socket!\n"); |
| #endif |
| return SOCKET_I(file->f_path.dentry->d_inode); |
| } |
| return NULL; |
| } |
| |
| void |
| smb_close_socket(struct smb_sb_info *server) |
| { |
| struct file * file = server->sock_file; |
| |
| if (file) { |
| struct socket *sock = server_sock(server); |
| |
| VERBOSE("closing socket %p\n", sock); |
| sock->sk->sk_data_ready = server->data_ready; |
| server->sock_file = NULL; |
| fput(file); |
| } |
| } |
| |
| static int |
| smb_get_length(struct socket *socket, unsigned char *header) |
| { |
| int result; |
| |
| result = _recvfrom(socket, header, 4, MSG_PEEK); |
| if (result == -EAGAIN) |
| return -ENODATA; |
| if (result < 0) { |
| PARANOIA("recv error = %d\n", -result); |
| return result; |
| } |
| if (result < 4) |
| return -ENODATA; |
| |
| switch (header[0]) { |
| case 0x00: |
| case 0x82: |
| break; |
| |
| case 0x85: |
| DEBUG1("Got SESSION KEEP ALIVE\n"); |
| _recvfrom(socket, header, 4, 0); /* read away */ |
| return -ENODATA; |
| |
| default: |
| PARANOIA("Invalid NBT packet, code=%x\n", header[0]); |
| return -EIO; |
| } |
| |
| /* The length in the RFC NB header is the raw data length */ |
| return smb_len(header); |
| } |
| |
| int |
| smb_recv_available(struct smb_sb_info *server) |
| { |
| mm_segment_t oldfs; |
| int avail, err; |
| struct socket *sock = server_sock(server); |
| |
| oldfs = get_fs(); |
| set_fs(get_ds()); |
| err = sock->ops->ioctl(sock, SIOCINQ, (unsigned long) &avail); |
| set_fs(oldfs); |
| return (err >= 0) ? avail : err; |
| } |
| |
| /* |
| * Adjust the kvec to move on 'n' bytes (from nfs/sunrpc) |
| */ |
| static int |
| smb_move_iov(struct kvec **data, size_t *num, struct kvec *vec, unsigned amount) |
| { |
| struct kvec *iv = *data; |
| int i; |
| int len; |
| |
| /* |
| * Eat any sent kvecs |
| */ |
| while (iv->iov_len <= amount) { |
| amount -= iv->iov_len; |
| iv++; |
| (*num)--; |
| } |
| |
| /* |
| * And chew down the partial one |
| */ |
| vec[0].iov_len = iv->iov_len-amount; |
| vec[0].iov_base =((unsigned char *)iv->iov_base)+amount; |
| iv++; |
| |
| len = vec[0].iov_len; |
| |
| /* |
| * And copy any others |
| */ |
| for (i = 1; i < *num; i++) { |
| vec[i] = *iv++; |
| len += vec[i].iov_len; |
| } |
| |
| *data = vec; |
| return len; |
| } |
| |
| /* |
| * smb_receive_header |
| * Only called by the smbiod thread. |
| */ |
| int |
| smb_receive_header(struct smb_sb_info *server) |
| { |
| struct socket *sock; |
| int result = 0; |
| unsigned char peek_buf[4]; |
| |
| result = -EIO; |
| sock = server_sock(server); |
| if (!sock) |
| goto out; |
| if (sock->sk->sk_state != TCP_ESTABLISHED) |
| goto out; |
| |
| if (!server->smb_read) { |
| result = smb_get_length(sock, peek_buf); |
| if (result < 0) { |
| if (result == -ENODATA) |
| result = 0; |
| goto out; |
| } |
| server->smb_len = result + 4; |
| |
| if (server->smb_len < SMB_HEADER_LEN) { |
| PARANOIA("short packet: %d\n", result); |
| server->rstate = SMB_RECV_DROP; |
| result = -EIO; |
| goto out; |
| } |
| if (server->smb_len > SMB_MAX_PACKET_SIZE) { |
| PARANOIA("long packet: %d\n", result); |
| server->rstate = SMB_RECV_DROP; |
| result = -EIO; |
| goto out; |
| } |
| } |
| |
| result = _recvfrom(sock, server->header + server->smb_read, |
| SMB_HEADER_LEN - server->smb_read, 0); |
| VERBOSE("_recvfrom: %d\n", result); |
| if (result < 0) { |
| VERBOSE("receive error: %d\n", result); |
| goto out; |
| } |
| server->smb_read += result; |
| |
| if (server->smb_read == SMB_HEADER_LEN) |
| server->rstate = SMB_RECV_HCOMPLETE; |
| out: |
| return result; |
| } |
| |
| static char drop_buffer[PAGE_SIZE]; |
| |
| /* |
| * smb_receive_drop - read and throw away the data |
| * Only called by the smbiod thread. |
| * |
| * FIXME: we are in the kernel, could we just tell the socket that we want |
| * to drop stuff from the buffer? |
| */ |
| int |
| smb_receive_drop(struct smb_sb_info *server) |
| { |
| struct socket *sock; |
| unsigned int flags; |
| struct kvec iov; |
| struct msghdr msg; |
| int rlen = smb_len(server->header) - server->smb_read + 4; |
| int result = -EIO; |
| |
| if (rlen > PAGE_SIZE) |
| rlen = PAGE_SIZE; |
| |
| sock = server_sock(server); |
| if (!sock) |
| goto out; |
| if (sock->sk->sk_state != TCP_ESTABLISHED) |
| goto out; |
| |
| flags = MSG_DONTWAIT | MSG_NOSIGNAL; |
| iov.iov_base = drop_buffer; |
| iov.iov_len = PAGE_SIZE; |
| msg.msg_flags = flags; |
| msg.msg_name = NULL; |
| msg.msg_namelen = 0; |
| msg.msg_control = NULL; |
| |
| result = kernel_recvmsg(sock, &msg, &iov, 1, rlen, flags); |
| |
| VERBOSE("read: %d\n", result); |
| if (result < 0) { |
| VERBOSE("receive error: %d\n", result); |
| goto out; |
| } |
| server->smb_read += result; |
| |
| if (server->smb_read >= server->smb_len) |
| server->rstate = SMB_RECV_END; |
| |
| out: |
| return result; |
| } |
| |
| /* |
| * smb_receive |
| * Only called by the smbiod thread. |
| */ |
| int |
| smb_receive(struct smb_sb_info *server, struct smb_request *req) |
| { |
| struct socket *sock; |
| unsigned int flags; |
| struct kvec iov[4]; |
| struct kvec *p = req->rq_iov; |
| size_t num = req->rq_iovlen; |
| struct msghdr msg; |
| int rlen; |
| int result = -EIO; |
| |
| sock = server_sock(server); |
| if (!sock) |
| goto out; |
| if (sock->sk->sk_state != TCP_ESTABLISHED) |
| goto out; |
| |
| flags = MSG_DONTWAIT | MSG_NOSIGNAL; |
| msg.msg_flags = flags; |
| msg.msg_name = NULL; |
| msg.msg_namelen = 0; |
| msg.msg_control = NULL; |
| |
| /* Dont repeat bytes and count available bufferspace */ |
| rlen = smb_move_iov(&p, &num, iov, req->rq_bytes_recvd); |
| if (req->rq_rlen < rlen) |
| rlen = req->rq_rlen; |
| |
| result = kernel_recvmsg(sock, &msg, p, num, rlen, flags); |
| |
| VERBOSE("read: %d\n", result); |
| if (result < 0) { |
| VERBOSE("receive error: %d\n", result); |
| goto out; |
| } |
| req->rq_bytes_recvd += result; |
| server->smb_read += result; |
| |
| out: |
| return result; |
| } |
| |
| /* |
| * Try to send a SMB request. This may return after sending only parts of the |
| * request. SMB_REQ_TRANSMITTED will be set if a request was fully sent. |
| * |
| * Parts of this was taken from xprt_sendmsg from net/sunrpc/xprt.c |
| */ |
| int |
| smb_send_request(struct smb_request *req) |
| { |
| struct smb_sb_info *server = req->rq_server; |
| struct socket *sock; |
| struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT}; |
| int slen = req->rq_slen - req->rq_bytes_sent; |
| int result = -EIO; |
| struct kvec iov[4]; |
| struct kvec *p = req->rq_iov; |
| size_t num = req->rq_iovlen; |
| |
| sock = server_sock(server); |
| if (!sock) |
| goto out; |
| if (sock->sk->sk_state != TCP_ESTABLISHED) |
| goto out; |
| |
| /* Dont repeat bytes */ |
| if (req->rq_bytes_sent) |
| smb_move_iov(&p, &num, iov, req->rq_bytes_sent); |
| |
| result = kernel_sendmsg(sock, &msg, p, num, slen); |
| |
| if (result >= 0) { |
| req->rq_bytes_sent += result; |
| if (req->rq_bytes_sent >= req->rq_slen) |
| req->rq_flags |= SMB_REQ_TRANSMITTED; |
| } |
| out: |
| return result; |
| } |