|
@@ -320,27 +320,24 @@ requeue_echo:
|
|
}
|
|
}
|
|
|
|
|
|
static bool
|
|
static bool
|
|
-allocate_buffers(char **bigbuf, char **smallbuf, unsigned int size,
|
|
|
|
- bool is_large_buf)
|
|
|
|
|
|
+allocate_buffers(struct TCP_Server_Info *server)
|
|
{
|
|
{
|
|
- char *bbuf = *bigbuf, *sbuf = *smallbuf;
|
|
|
|
-
|
|
|
|
- if (bbuf == NULL) {
|
|
|
|
- bbuf = (char *)cifs_buf_get();
|
|
|
|
- if (!bbuf) {
|
|
|
|
|
|
+ if (!server->bigbuf) {
|
|
|
|
+ server->bigbuf = (char *)cifs_buf_get();
|
|
|
|
+ if (!server->bigbuf) {
|
|
cERROR(1, "No memory for large SMB response");
|
|
cERROR(1, "No memory for large SMB response");
|
|
msleep(3000);
|
|
msleep(3000);
|
|
/* retry will check if exiting */
|
|
/* retry will check if exiting */
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
- } else if (is_large_buf) {
|
|
|
|
|
|
+ } else if (server->large_buf) {
|
|
/* we are reusing a dirty large buf, clear its start */
|
|
/* we are reusing a dirty large buf, clear its start */
|
|
- memset(bbuf, 0, size);
|
|
|
|
|
|
+ memset(server->bigbuf, 0, sizeof(struct smb_hdr));
|
|
}
|
|
}
|
|
|
|
|
|
- if (sbuf == NULL) {
|
|
|
|
- sbuf = (char *)cifs_small_buf_get();
|
|
|
|
- if (!sbuf) {
|
|
|
|
|
|
+ if (!server->smallbuf) {
|
|
|
|
+ server->smallbuf = (char *)cifs_small_buf_get();
|
|
|
|
+ if (!server->smallbuf) {
|
|
cERROR(1, "No memory for SMB response");
|
|
cERROR(1, "No memory for SMB response");
|
|
msleep(1000);
|
|
msleep(1000);
|
|
/* retry will check if exiting */
|
|
/* retry will check if exiting */
|
|
@@ -349,12 +346,9 @@ allocate_buffers(char **bigbuf, char **smallbuf, unsigned int size,
|
|
/* beginning of smb buffer is cleared in our buf_get */
|
|
/* beginning of smb buffer is cleared in our buf_get */
|
|
} else {
|
|
} else {
|
|
/* if existing small buf clear beginning */
|
|
/* if existing small buf clear beginning */
|
|
- memset(sbuf, 0, size);
|
|
|
|
|
|
+ memset(server->smallbuf, 0, sizeof(struct smb_hdr));
|
|
}
|
|
}
|
|
|
|
|
|
- *bigbuf = bbuf;
|
|
|
|
- *smallbuf = sbuf;
|
|
|
|
-
|
|
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -375,14 +369,72 @@ server_unresponsive(struct TCP_Server_Info *server)
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
-static int
|
|
|
|
-read_from_socket(struct TCP_Server_Info *server, char *buf,
|
|
|
|
- unsigned int to_read)
|
|
|
|
|
|
+/*
|
|
|
|
+ * kvec_array_init - clone a kvec array, and advance into it
|
|
|
|
+ * @new: pointer to memory for cloned array
|
|
|
|
+ * @iov: pointer to original array
|
|
|
|
+ * @nr_segs: number of members in original array
|
|
|
|
+ * @bytes: number of bytes to advance into the cloned array
|
|
|
|
+ *
|
|
|
|
+ * This function will copy the array provided in iov to a section of memory
|
|
|
|
+ * and advance the specified number of bytes into the new array. It returns
|
|
|
|
+ * the number of segments in the new array. "new" must be at least as big as
|
|
|
|
+ * the original iov array.
|
|
|
|
+ */
|
|
|
|
+static unsigned int
|
|
|
|
+kvec_array_init(struct kvec *new, struct kvec *iov, unsigned int nr_segs,
|
|
|
|
+ size_t bytes)
|
|
|
|
+{
|
|
|
|
+ size_t base = 0;
|
|
|
|
+
|
|
|
|
+ while (bytes || !iov->iov_len) {
|
|
|
|
+ int copy = min(bytes, iov->iov_len);
|
|
|
|
+
|
|
|
|
+ bytes -= copy;
|
|
|
|
+ base += copy;
|
|
|
|
+ if (iov->iov_len == base) {
|
|
|
|
+ iov++;
|
|
|
|
+ nr_segs--;
|
|
|
|
+ base = 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ memcpy(new, iov, sizeof(*iov) * nr_segs);
|
|
|
|
+ new->iov_base += base;
|
|
|
|
+ new->iov_len -= base;
|
|
|
|
+ return nr_segs;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct kvec *
|
|
|
|
+get_server_iovec(struct TCP_Server_Info *server, unsigned int nr_segs)
|
|
|
|
+{
|
|
|
|
+ struct kvec *new_iov;
|
|
|
|
+
|
|
|
|
+ if (server->iov && nr_segs <= server->nr_iov)
|
|
|
|
+ return server->iov;
|
|
|
|
+
|
|
|
|
+ /* not big enough -- allocate a new one and release the old */
|
|
|
|
+ new_iov = kmalloc(sizeof(*new_iov) * nr_segs, GFP_NOFS);
|
|
|
|
+ if (new_iov) {
|
|
|
|
+ kfree(server->iov);
|
|
|
|
+ server->iov = new_iov;
|
|
|
|
+ server->nr_iov = nr_segs;
|
|
|
|
+ }
|
|
|
|
+ return new_iov;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int
|
|
|
|
+cifs_readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig,
|
|
|
|
+ unsigned int nr_segs, unsigned int to_read)
|
|
{
|
|
{
|
|
int length = 0;
|
|
int length = 0;
|
|
int total_read;
|
|
int total_read;
|
|
|
|
+ unsigned int segs;
|
|
struct msghdr smb_msg;
|
|
struct msghdr smb_msg;
|
|
- struct kvec iov;
|
|
|
|
|
|
+ struct kvec *iov;
|
|
|
|
+
|
|
|
|
+ iov = get_server_iovec(server, nr_segs);
|
|
|
|
+ if (!iov)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
|
|
smb_msg.msg_control = NULL;
|
|
smb_msg.msg_control = NULL;
|
|
smb_msg.msg_controllen = 0;
|
|
smb_msg.msg_controllen = 0;
|
|
@@ -393,10 +445,11 @@ read_from_socket(struct TCP_Server_Info *server, char *buf,
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
- iov.iov_base = buf + total_read;
|
|
|
|
- iov.iov_len = to_read;
|
|
|
|
- length = kernel_recvmsg(server->ssocket, &smb_msg, &iov, 1,
|
|
|
|
- to_read, 0);
|
|
|
|
|
|
+ segs = kvec_array_init(iov, iov_orig, nr_segs, total_read);
|
|
|
|
+
|
|
|
|
+ length = kernel_recvmsg(server->ssocket, &smb_msg,
|
|
|
|
+ iov, segs, to_read, 0);
|
|
|
|
+
|
|
if (server->tcpStatus == CifsExiting) {
|
|
if (server->tcpStatus == CifsExiting) {
|
|
total_read = -ESHUTDOWN;
|
|
total_read = -ESHUTDOWN;
|
|
break;
|
|
break;
|
|
@@ -426,6 +479,18 @@ read_from_socket(struct TCP_Server_Info *server, char *buf,
|
|
return total_read;
|
|
return total_read;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+int
|
|
|
|
+cifs_read_from_socket(struct TCP_Server_Info *server, char *buf,
|
|
|
|
+ unsigned int to_read)
|
|
|
|
+{
|
|
|
|
+ struct kvec iov;
|
|
|
|
+
|
|
|
|
+ iov.iov_base = buf;
|
|
|
|
+ iov.iov_len = to_read;
|
|
|
|
+
|
|
|
|
+ return cifs_readv_from_socket(server, &iov, 1, to_read);
|
|
|
|
+}
|
|
|
|
+
|
|
static bool
|
|
static bool
|
|
is_smb_response(struct TCP_Server_Info *server, unsigned char type)
|
|
is_smb_response(struct TCP_Server_Info *server, unsigned char type)
|
|
{
|
|
{
|
|
@@ -471,61 +536,76 @@ is_smb_response(struct TCP_Server_Info *server, unsigned char type)
|
|
}
|
|
}
|
|
|
|
|
|
static struct mid_q_entry *
|
|
static struct mid_q_entry *
|
|
-find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf,
|
|
|
|
- int *length, bool is_large_buf, bool *is_multi_rsp, char **bigbuf)
|
|
|
|
|
|
+find_mid(struct TCP_Server_Info *server, struct smb_hdr *buf)
|
|
{
|
|
{
|
|
- struct mid_q_entry *mid = NULL, *tmp_mid, *ret = NULL;
|
|
|
|
|
|
+ struct mid_q_entry *mid;
|
|
|
|
|
|
spin_lock(&GlobalMid_Lock);
|
|
spin_lock(&GlobalMid_Lock);
|
|
- list_for_each_entry_safe(mid, tmp_mid, &server->pending_mid_q, qhead) {
|
|
|
|
- if (mid->mid != buf->Mid ||
|
|
|
|
- mid->midState != MID_REQUEST_SUBMITTED ||
|
|
|
|
- mid->command != buf->Command)
|
|
|
|
- continue;
|
|
|
|
-
|
|
|
|
- if (*length == 0 && check2ndT2(buf) > 0) {
|
|
|
|
- /* We have a multipart transact2 resp */
|
|
|
|
- *is_multi_rsp = true;
|
|
|
|
- if (mid->resp_buf) {
|
|
|
|
- /* merge response - fix up 1st*/
|
|
|
|
- *length = coalesce_t2(buf, mid->resp_buf);
|
|
|
|
- if (*length > 0) {
|
|
|
|
- *length = 0;
|
|
|
|
- mid->multiRsp = true;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- /* All parts received or packet is malformed. */
|
|
|
|
- mid->multiEnd = true;
|
|
|
|
- goto multi_t2_fnd;
|
|
|
|
- }
|
|
|
|
- if (!is_large_buf) {
|
|
|
|
- /*FIXME: switch to already allocated largebuf?*/
|
|
|
|
- cERROR(1, "1st trans2 resp needs bigbuf");
|
|
|
|
- } else {
|
|
|
|
- /* Have first buffer */
|
|
|
|
- mid->resp_buf = buf;
|
|
|
|
- mid->largeBuf = true;
|
|
|
|
- *bigbuf = NULL;
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
|
|
+ list_for_each_entry(mid, &server->pending_mid_q, qhead) {
|
|
|
|
+ if (mid->mid == buf->Mid &&
|
|
|
|
+ mid->midState == MID_REQUEST_SUBMITTED &&
|
|
|
|
+ mid->command == buf->Command) {
|
|
|
|
+ spin_unlock(&GlobalMid_Lock);
|
|
|
|
+ return mid;
|
|
}
|
|
}
|
|
- mid->resp_buf = buf;
|
|
|
|
- mid->largeBuf = is_large_buf;
|
|
|
|
-multi_t2_fnd:
|
|
|
|
- if (*length == 0)
|
|
|
|
- mid->midState = MID_RESPONSE_RECEIVED;
|
|
|
|
- else
|
|
|
|
- mid->midState = MID_RESPONSE_MALFORMED;
|
|
|
|
|
|
+ }
|
|
|
|
+ spin_unlock(&GlobalMid_Lock);
|
|
|
|
+ return NULL;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void
|
|
|
|
+dequeue_mid(struct mid_q_entry *mid, bool malformed)
|
|
|
|
+{
|
|
#ifdef CONFIG_CIFS_STATS2
|
|
#ifdef CONFIG_CIFS_STATS2
|
|
- mid->when_received = jiffies;
|
|
|
|
|
|
+ mid->when_received = jiffies;
|
|
#endif
|
|
#endif
|
|
- list_del_init(&mid->qhead);
|
|
|
|
- ret = mid;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
|
|
+ spin_lock(&GlobalMid_Lock);
|
|
|
|
+ if (!malformed)
|
|
|
|
+ mid->midState = MID_RESPONSE_RECEIVED;
|
|
|
|
+ else
|
|
|
|
+ mid->midState = MID_RESPONSE_MALFORMED;
|
|
|
|
+ list_del_init(&mid->qhead);
|
|
spin_unlock(&GlobalMid_Lock);
|
|
spin_unlock(&GlobalMid_Lock);
|
|
|
|
+}
|
|
|
|
|
|
- return ret;
|
|
|
|
|
|
+static void
|
|
|
|
+handle_mid(struct mid_q_entry *mid, struct TCP_Server_Info *server,
|
|
|
|
+ struct smb_hdr *buf, int malformed)
|
|
|
|
+{
|
|
|
|
+ if (malformed == 0 && check2ndT2(buf) > 0) {
|
|
|
|
+ mid->multiRsp = true;
|
|
|
|
+ if (mid->resp_buf) {
|
|
|
|
+ /* merge response - fix up 1st*/
|
|
|
|
+ malformed = coalesce_t2(buf, mid->resp_buf);
|
|
|
|
+ if (malformed > 0)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ /* All parts received or packet is malformed. */
|
|
|
|
+ mid->multiEnd = true;
|
|
|
|
+ return dequeue_mid(mid, malformed);
|
|
|
|
+ }
|
|
|
|
+ if (!server->large_buf) {
|
|
|
|
+ /*FIXME: switch to already allocated largebuf?*/
|
|
|
|
+ cERROR(1, "1st trans2 resp needs bigbuf");
|
|
|
|
+ } else {
|
|
|
|
+ /* Have first buffer */
|
|
|
|
+ mid->resp_buf = buf;
|
|
|
|
+ mid->largeBuf = true;
|
|
|
|
+ server->bigbuf = NULL;
|
|
|
|
+ }
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ mid->resp_buf = buf;
|
|
|
|
+ mid->largeBuf = server->large_buf;
|
|
|
|
+ /* Was previous buf put in mpx struct for multi-rsp? */
|
|
|
|
+ if (!mid->multiRsp) {
|
|
|
|
+ /* smb buffer will be freed by user thread */
|
|
|
|
+ if (server->large_buf)
|
|
|
|
+ server->bigbuf = NULL;
|
|
|
|
+ else
|
|
|
|
+ server->smallbuf = NULL;
|
|
|
|
+ }
|
|
|
|
+ dequeue_mid(mid, malformed);
|
|
}
|
|
}
|
|
|
|
|
|
static void clean_demultiplex_info(struct TCP_Server_Info *server)
|
|
static void clean_demultiplex_info(struct TCP_Server_Info *server)
|
|
@@ -615,6 +695,7 @@ static void clean_demultiplex_info(struct TCP_Server_Info *server)
|
|
}
|
|
}
|
|
|
|
|
|
kfree(server->hostname);
|
|
kfree(server->hostname);
|
|
|
|
+ kfree(server->iov);
|
|
kfree(server);
|
|
kfree(server);
|
|
|
|
|
|
length = atomic_dec_return(&tcpSesAllocCount);
|
|
length = atomic_dec_return(&tcpSesAllocCount);
|
|
@@ -623,18 +704,71 @@ static void clean_demultiplex_info(struct TCP_Server_Info *server)
|
|
GFP_KERNEL);
|
|
GFP_KERNEL);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int
|
|
|
|
+standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid)
|
|
|
|
+{
|
|
|
|
+ int length;
|
|
|
|
+ char *buf = server->smallbuf;
|
|
|
|
+ struct smb_hdr *smb_buffer = (struct smb_hdr *)buf;
|
|
|
|
+ unsigned int pdu_length = be32_to_cpu(smb_buffer->smb_buf_length);
|
|
|
|
+
|
|
|
|
+ /* make sure this will fit in a large buffer */
|
|
|
|
+ if (pdu_length > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) {
|
|
|
|
+ cERROR(1, "SMB response too long (%u bytes)",
|
|
|
|
+ pdu_length);
|
|
|
|
+ cifs_reconnect(server);
|
|
|
|
+ wake_up(&server->response_q);
|
|
|
|
+ return -EAGAIN;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* switch to large buffer if too big for a small one */
|
|
|
|
+ if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) {
|
|
|
|
+ server->large_buf = true;
|
|
|
|
+ memcpy(server->bigbuf, server->smallbuf, server->total_read);
|
|
|
|
+ buf = server->bigbuf;
|
|
|
|
+ smb_buffer = (struct smb_hdr *)buf;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* now read the rest */
|
|
|
|
+ length = cifs_read_from_socket(server,
|
|
|
|
+ buf + sizeof(struct smb_hdr) - 1,
|
|
|
|
+ pdu_length - sizeof(struct smb_hdr) + 1 + 4);
|
|
|
|
+ if (length < 0)
|
|
|
|
+ return length;
|
|
|
|
+ server->total_read += length;
|
|
|
|
+
|
|
|
|
+ dump_smb(smb_buffer, server->total_read);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * We know that we received enough to get to the MID as we
|
|
|
|
+ * checked the pdu_length earlier. Now check to see
|
|
|
|
+ * if the rest of the header is OK. We borrow the length
|
|
|
|
+ * var for the rest of the loop to avoid a new stack var.
|
|
|
|
+ *
|
|
|
|
+ * 48 bytes is enough to display the header and a little bit
|
|
|
|
+ * into the payload for debugging purposes.
|
|
|
|
+ */
|
|
|
|
+ length = checkSMB(smb_buffer, smb_buffer->Mid, server->total_read);
|
|
|
|
+ if (length != 0)
|
|
|
|
+ cifs_dump_mem("Bad SMB: ", buf,
|
|
|
|
+ min_t(unsigned int, server->total_read, 48));
|
|
|
|
+
|
|
|
|
+ if (mid)
|
|
|
|
+ handle_mid(mid, server, smb_buffer, length);
|
|
|
|
+
|
|
|
|
+ return length;
|
|
|
|
+}
|
|
|
|
+
|
|
static int
|
|
static int
|
|
cifs_demultiplex_thread(void *p)
|
|
cifs_demultiplex_thread(void *p)
|
|
{
|
|
{
|
|
int length;
|
|
int length;
|
|
struct TCP_Server_Info *server = p;
|
|
struct TCP_Server_Info *server = p;
|
|
- unsigned int pdu_length, total_read;
|
|
|
|
- char *buf = NULL, *bigbuf = NULL, *smallbuf = NULL;
|
|
|
|
|
|
+ unsigned int pdu_length;
|
|
|
|
+ char *buf = NULL;
|
|
struct smb_hdr *smb_buffer = NULL;
|
|
struct smb_hdr *smb_buffer = NULL;
|
|
struct task_struct *task_to_wake = NULL;
|
|
struct task_struct *task_to_wake = NULL;
|
|
struct mid_q_entry *mid_entry;
|
|
struct mid_q_entry *mid_entry;
|
|
- bool isLargeBuf = false;
|
|
|
|
- bool isMultiRsp = false;
|
|
|
|
|
|
|
|
current->flags |= PF_MEMALLOC;
|
|
current->flags |= PF_MEMALLOC;
|
|
cFYI(1, "Demultiplex PID: %d", task_pid_nr(current));
|
|
cFYI(1, "Demultiplex PID: %d", task_pid_nr(current));
|
|
@@ -649,20 +783,18 @@ cifs_demultiplex_thread(void *p)
|
|
if (try_to_freeze())
|
|
if (try_to_freeze())
|
|
continue;
|
|
continue;
|
|
|
|
|
|
- if (!allocate_buffers(&bigbuf, &smallbuf,
|
|
|
|
- sizeof(struct smb_hdr), isLargeBuf))
|
|
|
|
|
|
+ if (!allocate_buffers(server))
|
|
continue;
|
|
continue;
|
|
|
|
|
|
- isLargeBuf = false;
|
|
|
|
- isMultiRsp = false;
|
|
|
|
- smb_buffer = (struct smb_hdr *)smallbuf;
|
|
|
|
- buf = smallbuf;
|
|
|
|
|
|
+ server->large_buf = false;
|
|
|
|
+ smb_buffer = (struct smb_hdr *)server->smallbuf;
|
|
|
|
+ buf = server->smallbuf;
|
|
pdu_length = 4; /* enough to get RFC1001 header */
|
|
pdu_length = 4; /* enough to get RFC1001 header */
|
|
|
|
|
|
- length = read_from_socket(server, buf, pdu_length);
|
|
|
|
|
|
+ length = cifs_read_from_socket(server, buf, pdu_length);
|
|
if (length < 0)
|
|
if (length < 0)
|
|
continue;
|
|
continue;
|
|
- total_read = length;
|
|
|
|
|
|
+ server->total_read = length;
|
|
|
|
|
|
/*
|
|
/*
|
|
* The right amount was read from socket - 4 bytes,
|
|
* The right amount was read from socket - 4 bytes,
|
|
@@ -674,64 +806,42 @@ cifs_demultiplex_thread(void *p)
|
|
if (!is_smb_response(server, buf[0]))
|
|
if (!is_smb_response(server, buf[0]))
|
|
continue;
|
|
continue;
|
|
|
|
|
|
- /* check the length */
|
|
|
|
- if ((pdu_length > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) ||
|
|
|
|
- (pdu_length < sizeof(struct smb_hdr) - 1 - 4)) {
|
|
|
|
- cERROR(1, "Invalid size SMB length %d pdu_length %d",
|
|
|
|
- 4, pdu_length + 4);
|
|
|
|
|
|
+ /* make sure we have enough to get to the MID */
|
|
|
|
+ if (pdu_length < sizeof(struct smb_hdr) - 1 - 4) {
|
|
|
|
+ cERROR(1, "SMB response too short (%u bytes)",
|
|
|
|
+ pdu_length);
|
|
cifs_reconnect(server);
|
|
cifs_reconnect(server);
|
|
wake_up(&server->response_q);
|
|
wake_up(&server->response_q);
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
- /* else length ok */
|
|
|
|
- if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) {
|
|
|
|
- isLargeBuf = true;
|
|
|
|
- memcpy(bigbuf, smallbuf, 4);
|
|
|
|
- smb_buffer = (struct smb_hdr *)bigbuf;
|
|
|
|
- buf = bigbuf;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- length = read_from_socket(server, buf + 4, pdu_length);
|
|
|
|
|
|
+ /* read down to the MID */
|
|
|
|
+ length = cifs_read_from_socket(server, buf + 4,
|
|
|
|
+ sizeof(struct smb_hdr) - 1 - 4);
|
|
if (length < 0)
|
|
if (length < 0)
|
|
continue;
|
|
continue;
|
|
- total_read += length;
|
|
|
|
|
|
+ server->total_read += length;
|
|
|
|
|
|
- dump_smb(smb_buffer, total_read);
|
|
|
|
|
|
+ mid_entry = find_mid(server, smb_buffer);
|
|
|
|
|
|
- /*
|
|
|
|
- * We know that we received enough to get to the MID as we
|
|
|
|
- * checked the pdu_length earlier. Now check to see
|
|
|
|
- * if the rest of the header is OK. We borrow the length
|
|
|
|
- * var for the rest of the loop to avoid a new stack var.
|
|
|
|
- *
|
|
|
|
- * 48 bytes is enough to display the header and a little bit
|
|
|
|
- * into the payload for debugging purposes.
|
|
|
|
- */
|
|
|
|
- length = checkSMB(smb_buffer, smb_buffer->Mid, total_read);
|
|
|
|
- if (length != 0)
|
|
|
|
- cifs_dump_mem("Bad SMB: ", buf,
|
|
|
|
- min_t(unsigned int, total_read, 48));
|
|
|
|
|
|
+ if (!mid_entry || !mid_entry->receive)
|
|
|
|
+ length = standard_receive3(server, mid_entry);
|
|
|
|
+ else
|
|
|
|
+ length = mid_entry->receive(server, mid_entry);
|
|
|
|
|
|
- server->lstrp = jiffies;
|
|
|
|
|
|
+ if (length < 0)
|
|
|
|
+ continue;
|
|
|
|
|
|
- mid_entry = find_cifs_mid(server, smb_buffer, &length,
|
|
|
|
- isLargeBuf, &isMultiRsp, &bigbuf);
|
|
|
|
|
|
+ if (server->large_buf) {
|
|
|
|
+ buf = server->bigbuf;
|
|
|
|
+ smb_buffer = (struct smb_hdr *)buf;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ server->lstrp = jiffies;
|
|
if (mid_entry != NULL) {
|
|
if (mid_entry != NULL) {
|
|
- mid_entry->callback(mid_entry);
|
|
|
|
- /* Was previous buf put in mpx struct for multi-rsp? */
|
|
|
|
- if (!isMultiRsp) {
|
|
|
|
- /* smb buffer will be freed by user thread */
|
|
|
|
- if (isLargeBuf)
|
|
|
|
- bigbuf = NULL;
|
|
|
|
- else
|
|
|
|
- smallbuf = NULL;
|
|
|
|
- }
|
|
|
|
- } else if (length != 0) {
|
|
|
|
- /* response sanity checks failed */
|
|
|
|
- continue;
|
|
|
|
- } else if (!is_valid_oplock_break(smb_buffer, server) &&
|
|
|
|
- !isMultiRsp) {
|
|
|
|
|
|
+ if (!mid_entry->multiRsp || mid_entry->multiEnd)
|
|
|
|
+ mid_entry->callback(mid_entry);
|
|
|
|
+ } else if (!is_valid_oplock_break(smb_buffer, server)) {
|
|
cERROR(1, "No task to wake, unknown frame received! "
|
|
cERROR(1, "No task to wake, unknown frame received! "
|
|
"NumMids %d", atomic_read(&midCount));
|
|
"NumMids %d", atomic_read(&midCount));
|
|
cifs_dump_mem("Received Data is: ", buf,
|
|
cifs_dump_mem("Received Data is: ", buf,
|
|
@@ -745,9 +855,9 @@ cifs_demultiplex_thread(void *p)
|
|
} /* end while !EXITING */
|
|
} /* end while !EXITING */
|
|
|
|
|
|
/* buffer usually freed in free_mid - need to free it here on exit */
|
|
/* buffer usually freed in free_mid - need to free it here on exit */
|
|
- cifs_buf_release(bigbuf);
|
|
|
|
- if (smallbuf) /* no sense logging a debug message if NULL */
|
|
|
|
- cifs_small_buf_release(smallbuf);
|
|
|
|
|
|
+ cifs_buf_release(server->bigbuf);
|
|
|
|
+ if (server->smallbuf) /* no sense logging a debug message if NULL */
|
|
|
|
+ cifs_small_buf_release(server->smallbuf);
|
|
|
|
|
|
task_to_wake = xchg(&server->tsk, NULL);
|
|
task_to_wake = xchg(&server->tsk, NULL);
|
|
clean_demultiplex_info(server);
|
|
clean_demultiplex_info(server);
|
|
@@ -2200,16 +2310,16 @@ compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data)
|
|
(new->mnt_cifs_flags & CIFS_MOUNT_MASK))
|
|
(new->mnt_cifs_flags & CIFS_MOUNT_MASK))
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
- if (old->rsize != new->rsize)
|
|
|
|
- return 0;
|
|
|
|
-
|
|
|
|
/*
|
|
/*
|
|
- * We want to share sb only if we don't specify wsize or specified wsize
|
|
|
|
- * is greater or equal than existing one.
|
|
|
|
|
|
+ * We want to share sb only if we don't specify an r/wsize or
|
|
|
|
+ * specified r/wsize is greater than or equal to existing one.
|
|
*/
|
|
*/
|
|
if (new->wsize && new->wsize < old->wsize)
|
|
if (new->wsize && new->wsize < old->wsize)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
|
|
+ if (new->rsize && new->rsize < old->rsize)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
if (old->mnt_uid != new->mnt_uid || old->mnt_gid != new->mnt_gid)
|
|
if (old->mnt_uid != new->mnt_uid || old->mnt_gid != new->mnt_gid)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
@@ -2647,14 +2757,6 @@ void reset_cifs_unix_caps(int xid, struct cifs_tcon *tcon,
|
|
CIFS_MOUNT_POSIX_PATHS;
|
|
CIFS_MOUNT_POSIX_PATHS;
|
|
}
|
|
}
|
|
|
|
|
|
- if (cifs_sb && (cifs_sb->rsize > 127 * 1024)) {
|
|
|
|
- if ((cap & CIFS_UNIX_LARGE_READ_CAP) == 0) {
|
|
|
|
- cifs_sb->rsize = 127 * 1024;
|
|
|
|
- cFYI(DBG2, "larger reads not supported by srv");
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
cFYI(1, "Negotiate caps 0x%x", (int)cap);
|
|
cFYI(1, "Negotiate caps 0x%x", (int)cap);
|
|
#ifdef CONFIG_CIFS_DEBUG2
|
|
#ifdef CONFIG_CIFS_DEBUG2
|
|
if (cap & CIFS_UNIX_FCNTL_CAP)
|
|
if (cap & CIFS_UNIX_FCNTL_CAP)
|
|
@@ -2699,27 +2801,11 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info,
|
|
spin_lock_init(&cifs_sb->tlink_tree_lock);
|
|
spin_lock_init(&cifs_sb->tlink_tree_lock);
|
|
cifs_sb->tlink_tree = RB_ROOT;
|
|
cifs_sb->tlink_tree = RB_ROOT;
|
|
|
|
|
|
- if (pvolume_info->rsize > CIFSMaxBufSize) {
|
|
|
|
- cERROR(1, "rsize %d too large, using MaxBufSize",
|
|
|
|
- pvolume_info->rsize);
|
|
|
|
- cifs_sb->rsize = CIFSMaxBufSize;
|
|
|
|
- } else if ((pvolume_info->rsize) &&
|
|
|
|
- (pvolume_info->rsize <= CIFSMaxBufSize))
|
|
|
|
- cifs_sb->rsize = pvolume_info->rsize;
|
|
|
|
- else /* default */
|
|
|
|
- cifs_sb->rsize = CIFSMaxBufSize;
|
|
|
|
-
|
|
|
|
- if (cifs_sb->rsize < 2048) {
|
|
|
|
- cifs_sb->rsize = 2048;
|
|
|
|
- /* Windows ME may prefer this */
|
|
|
|
- cFYI(1, "readsize set to minimum: 2048");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/*
|
|
/*
|
|
- * Temporarily set wsize for matching superblock. If we end up using
|
|
|
|
- * new sb then cifs_negotiate_wsize will later negotiate it downward
|
|
|
|
- * if needed.
|
|
|
|
|
|
+ * Temporarily set r/wsize for matching superblock. If we end up using
|
|
|
|
+ * new sb then client will later negotiate it downward if needed.
|
|
*/
|
|
*/
|
|
|
|
+ cifs_sb->rsize = pvolume_info->rsize;
|
|
cifs_sb->wsize = pvolume_info->wsize;
|
|
cifs_sb->wsize = pvolume_info->wsize;
|
|
|
|
|
|
cifs_sb->mnt_uid = pvolume_info->linux_uid;
|
|
cifs_sb->mnt_uid = pvolume_info->linux_uid;
|
|
@@ -2794,29 +2880,41 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info,
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
- * When the server supports very large writes via POSIX extensions, we can
|
|
|
|
- * allow up to 2^24-1, minus the size of a WRITE_AND_X header, not including
|
|
|
|
- * the RFC1001 length.
|
|
|
|
|
|
+ * When the server supports very large reads and writes via POSIX extensions,
|
|
|
|
+ * we can allow up to 2^24-1, minus the size of a READ/WRITE_AND_X header, not
|
|
|
|
+ * including the RFC1001 length.
|
|
*
|
|
*
|
|
* Note that this might make for "interesting" allocation problems during
|
|
* Note that this might make for "interesting" allocation problems during
|
|
* writeback however as we have to allocate an array of pointers for the
|
|
* writeback however as we have to allocate an array of pointers for the
|
|
* pages. A 16M write means ~32kb page array with PAGE_CACHE_SIZE == 4096.
|
|
* pages. A 16M write means ~32kb page array with PAGE_CACHE_SIZE == 4096.
|
|
|
|
+ *
|
|
|
|
+ * For reads, there is a similar problem as we need to allocate an array
|
|
|
|
+ * of kvecs to handle the receive, though that should only need to be done
|
|
|
|
+ * once.
|
|
*/
|
|
*/
|
|
#define CIFS_MAX_WSIZE ((1<<24) - 1 - sizeof(WRITE_REQ) + 4)
|
|
#define CIFS_MAX_WSIZE ((1<<24) - 1 - sizeof(WRITE_REQ) + 4)
|
|
|
|
+#define CIFS_MAX_RSIZE ((1<<24) - sizeof(READ_RSP) + 4)
|
|
|
|
|
|
/*
|
|
/*
|
|
- * When the server doesn't allow large posix writes, only allow a wsize of
|
|
|
|
- * 2^17-1 minus the size of the WRITE_AND_X header. That allows for a write up
|
|
|
|
- * to the maximum size described by RFC1002.
|
|
|
|
|
|
+ * When the server doesn't allow large posix writes, only allow a rsize/wsize
|
|
|
|
+ * of 2^17-1 minus the size of the call header. That allows for a read or
|
|
|
|
+ * write up to the maximum size described by RFC1002.
|
|
*/
|
|
*/
|
|
#define CIFS_MAX_RFC1002_WSIZE ((1<<17) - 1 - sizeof(WRITE_REQ) + 4)
|
|
#define CIFS_MAX_RFC1002_WSIZE ((1<<17) - 1 - sizeof(WRITE_REQ) + 4)
|
|
|
|
+#define CIFS_MAX_RFC1002_RSIZE ((1<<17) - 1 - sizeof(READ_RSP) + 4)
|
|
|
|
|
|
/*
|
|
/*
|
|
* The default wsize is 1M. find_get_pages seems to return a maximum of 256
|
|
* The default wsize is 1M. find_get_pages seems to return a maximum of 256
|
|
* pages in a single call. With PAGE_CACHE_SIZE == 4k, this means we can fill
|
|
* pages in a single call. With PAGE_CACHE_SIZE == 4k, this means we can fill
|
|
* a single wsize request with a single call.
|
|
* a single wsize request with a single call.
|
|
*/
|
|
*/
|
|
-#define CIFS_DEFAULT_WSIZE (1024 * 1024)
|
|
|
|
|
|
+#define CIFS_DEFAULT_IOSIZE (1024 * 1024)
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Windows only supports a max of 60k reads. Default to that when posix
|
|
|
|
+ * extensions aren't in force.
|
|
|
|
+ */
|
|
|
|
+#define CIFS_DEFAULT_NON_POSIX_RSIZE (60 * 1024)
|
|
|
|
|
|
static unsigned int
|
|
static unsigned int
|
|
cifs_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info)
|
|
cifs_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info)
|
|
@@ -2824,7 +2922,7 @@ cifs_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info)
|
|
__u64 unix_cap = le64_to_cpu(tcon->fsUnixInfo.Capability);
|
|
__u64 unix_cap = le64_to_cpu(tcon->fsUnixInfo.Capability);
|
|
struct TCP_Server_Info *server = tcon->ses->server;
|
|
struct TCP_Server_Info *server = tcon->ses->server;
|
|
unsigned int wsize = pvolume_info->wsize ? pvolume_info->wsize :
|
|
unsigned int wsize = pvolume_info->wsize ? pvolume_info->wsize :
|
|
- CIFS_DEFAULT_WSIZE;
|
|
|
|
|
|
+ CIFS_DEFAULT_IOSIZE;
|
|
|
|
|
|
/* can server support 24-bit write sizes? (via UNIX extensions) */
|
|
/* can server support 24-bit write sizes? (via UNIX extensions) */
|
|
if (!tcon->unix_ext || !(unix_cap & CIFS_UNIX_LARGE_WRITE_CAP))
|
|
if (!tcon->unix_ext || !(unix_cap & CIFS_UNIX_LARGE_WRITE_CAP))
|
|
@@ -2847,6 +2945,50 @@ cifs_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info)
|
|
return wsize;
|
|
return wsize;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static unsigned int
|
|
|
|
+cifs_negotiate_rsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info)
|
|
|
|
+{
|
|
|
|
+ __u64 unix_cap = le64_to_cpu(tcon->fsUnixInfo.Capability);
|
|
|
|
+ struct TCP_Server_Info *server = tcon->ses->server;
|
|
|
|
+ unsigned int rsize, defsize;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Set default value...
|
|
|
|
+ *
|
|
|
|
+ * HACK alert! Ancient servers have very small buffers. Even though
|
|
|
|
+ * MS-CIFS indicates that servers are only limited by the client's
|
|
|
|
+ * bufsize for reads, testing against win98se shows that it throws
|
|
|
|
+ * INVALID_PARAMETER errors if you try to request too large a read.
|
|
|
|
+ *
|
|
|
|
+ * If the server advertises a MaxBufferSize of less than one page,
|
|
|
|
+ * assume that it also can't satisfy reads larger than that either.
|
|
|
|
+ *
|
|
|
|
+ * FIXME: Is there a better heuristic for this?
|
|
|
|
+ */
|
|
|
|
+ if (tcon->unix_ext && (unix_cap & CIFS_UNIX_LARGE_READ_CAP))
|
|
|
|
+ defsize = CIFS_DEFAULT_IOSIZE;
|
|
|
|
+ else if (server->capabilities & CAP_LARGE_READ_X)
|
|
|
|
+ defsize = CIFS_DEFAULT_NON_POSIX_RSIZE;
|
|
|
|
+ else if (server->maxBuf >= PAGE_CACHE_SIZE)
|
|
|
|
+ defsize = CIFSMaxBufSize;
|
|
|
|
+ else
|
|
|
|
+ defsize = server->maxBuf - sizeof(READ_RSP);
|
|
|
|
+
|
|
|
|
+ rsize = pvolume_info->rsize ? pvolume_info->rsize : defsize;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * no CAP_LARGE_READ_X? Then MS-CIFS states that we must limit this to
|
|
|
|
+ * the client's MaxBufferSize.
|
|
|
|
+ */
|
|
|
|
+ if (!(server->capabilities & CAP_LARGE_READ_X))
|
|
|
|
+ rsize = min_t(unsigned int, CIFSMaxBufSize, rsize);
|
|
|
|
+
|
|
|
|
+ /* hard limit of CIFS_MAX_RSIZE */
|
|
|
|
+ rsize = min_t(unsigned int, rsize, CIFS_MAX_RSIZE);
|
|
|
|
+
|
|
|
|
+ return rsize;
|
|
|
|
+}
|
|
|
|
+
|
|
static int
|
|
static int
|
|
is_path_accessible(int xid, struct cifs_tcon *tcon,
|
|
is_path_accessible(int xid, struct cifs_tcon *tcon,
|
|
struct cifs_sb_info *cifs_sb, const char *full_path)
|
|
struct cifs_sb_info *cifs_sb, const char *full_path)
|
|
@@ -3040,6 +3182,22 @@ cifs_get_volume_info(char *mount_data, const char *devname)
|
|
return volume_info;
|
|
return volume_info;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/* make sure ra_pages is a multiple of rsize */
|
|
|
|
+static inline unsigned int
|
|
|
|
+cifs_ra_pages(struct cifs_sb_info *cifs_sb)
|
|
|
|
+{
|
|
|
|
+ unsigned int reads;
|
|
|
|
+ unsigned int rsize_pages = cifs_sb->rsize / PAGE_CACHE_SIZE;
|
|
|
|
+
|
|
|
|
+ if (rsize_pages >= default_backing_dev_info.ra_pages)
|
|
|
|
+ return default_backing_dev_info.ra_pages;
|
|
|
|
+ else if (rsize_pages == 0)
|
|
|
|
+ return rsize_pages;
|
|
|
|
+
|
|
|
|
+ reads = default_backing_dev_info.ra_pages / rsize_pages;
|
|
|
|
+ return reads * rsize_pages;
|
|
|
|
+}
|
|
|
|
+
|
|
int
|
|
int
|
|
cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
|
|
cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
|
|
{
|
|
{
|
|
@@ -3058,8 +3216,6 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
|
|
if (rc)
|
|
if (rc)
|
|
return rc;
|
|
return rc;
|
|
|
|
|
|
- cifs_sb->bdi.ra_pages = default_backing_dev_info.ra_pages;
|
|
|
|
-
|
|
|
|
#ifdef CONFIG_CIFS_DFS_UPCALL
|
|
#ifdef CONFIG_CIFS_DFS_UPCALL
|
|
try_mount_again:
|
|
try_mount_again:
|
|
/* cleanup activities if we're chasing a referral */
|
|
/* cleanup activities if we're chasing a referral */
|
|
@@ -3124,14 +3280,11 @@ try_mount_again:
|
|
CIFSSMBQFSAttributeInfo(xid, tcon);
|
|
CIFSSMBQFSAttributeInfo(xid, tcon);
|
|
}
|
|
}
|
|
|
|
|
|
- if ((tcon->unix_ext == 0) && (cifs_sb->rsize > (1024 * 127))) {
|
|
|
|
- cifs_sb->rsize = 1024 * 127;
|
|
|
|
- cFYI(DBG2, "no very large read support, rsize now 127K");
|
|
|
|
- }
|
|
|
|
- if (!(tcon->ses->capabilities & CAP_LARGE_READ_X))
|
|
|
|
- cifs_sb->rsize = min(cifs_sb->rsize, CIFSMaxBufSize);
|
|
|
|
-
|
|
|
|
cifs_sb->wsize = cifs_negotiate_wsize(tcon, volume_info);
|
|
cifs_sb->wsize = cifs_negotiate_wsize(tcon, volume_info);
|
|
|
|
+ cifs_sb->rsize = cifs_negotiate_rsize(tcon, volume_info);
|
|
|
|
+
|
|
|
|
+ /* tune readahead according to rsize */
|
|
|
|
+ cifs_sb->bdi.ra_pages = cifs_ra_pages(cifs_sb);
|
|
|
|
|
|
remote_path_check:
|
|
remote_path_check:
|
|
#ifdef CONFIG_CIFS_DFS_UPCALL
|
|
#ifdef CONFIG_CIFS_DFS_UPCALL
|