aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/smbfs/sock.c
diff options
context:
space:
mode:
authorArnd Bergmann <arnd@arndb.de>2010-10-04 22:55:57 +0200
committerGreg Kroah-Hartman <gregkh@suse.de>2010-10-05 09:08:21 -0700
commit2116b7a473bf1c8d26998b477c294e7fe294921f (patch)
tree5f06aca6b425916f763d83fa4516bca51c8f9a60 /drivers/staging/smbfs/sock.c
parentStaging: autofs3: create TODO file (diff)
downloadlinux-dev-2116b7a473bf1c8d26998b477c294e7fe294921f.tar.xz
linux-dev-2116b7a473bf1c8d26998b477c294e7fe294921f.zip
smbfs: move to drivers/staging
smbfs has been scheduled for removal in 2.6.27, so maybe we can now move it to drivers/staging on the way out. smbfs still uses the big kernel lock and nobody is going to fix that, so we should be getting rid of it soon. This removes the 32 bit compat mount and ioctl handling code, which is implemented in common fs code, and moves all smbfs related files into drivers/staging/smbfs. Signed-off-by: Arnd Bergmann <arnd@arndb.de> Acked-by: Jeff Layton <jlayton@redhat.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/staging/smbfs/sock.c')
-rw-r--r--drivers/staging/smbfs/sock.c385
1 files changed, 385 insertions, 0 deletions
diff --git a/drivers/staging/smbfs/sock.c b/drivers/staging/smbfs/sock.c
new file mode 100644
index 000000000000..9e264090e611
--- /dev/null
+++ b/drivers/staging/smbfs/sock.c
@@ -0,0 +1,385 @@
+/*
+ * 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 <asm/uaccess.h>
+#include <asm/ioctls.h>
+
+#include "smb_fs.h"
+#include "smb.h"
+#include "smbno.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 = min_t(int, smb_move_iov(&p, &num, iov, req->rq_bytes_recvd),
+ (req->rq_rlen - req->rq_bytes_recvd));
+
+ 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;
+}