/fs/smbfs/sock.c
C | 388 lines | 282 code | 56 blank | 50 comment | 43 complexity | 0ad1e82d0972a1ae13c5b5f7d5663a36 MD5 | raw file
- /*
- * 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/smp_lock.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;
- }