From c974befa402b5eb2ed115b3083b5a46a4be85a9f Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 11 Oct 2011 06:41:32 -0400 Subject: cifs: untangle server->maxBuf and CIFSMaxBufSize server->maxBuf is the maximum SMB size (including header) that the server can handle. CIFSMaxBufSize is the maximum amount of data (sans header) that the client can handle. Currently maxBuf is being capped at CIFSMaxBufSize + the max headers size, and the two values are used somewhat interchangeably in the code. This makes little sense as these two values are not related at all. Separate them and make sure the code uses the right values in the right places. Signed-off-by: Jeff Layton Signed-off-by: Steve French --- fs/cifs/connect.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 71beb0201970..a0077a5e0669 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -181,7 +181,7 @@ cifs_reconnect(struct TCP_Server_Info *server) -EINVAL = invalid transact2 */ -static int check2ndT2(struct smb_hdr *pSMB, unsigned int maxBufSize) +static int check2ndT2(struct smb_hdr *pSMB) { struct smb_t2_rsp *pSMBt; int remaining; @@ -214,9 +214,9 @@ static int check2ndT2(struct smb_hdr *pSMB, unsigned int maxBufSize) cFYI(1, "missing %d bytes from transact2, check next response", remaining); - if (total_data_size > maxBufSize) { + if (total_data_size > CIFSMaxBufSize) { cERROR(1, "TotalDataSize %d is over maximum buffer %d", - total_data_size, maxBufSize); + total_data_size, CIFSMaxBufSize); return -EINVAL; } return remaining; @@ -486,7 +486,7 @@ find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, mid->command != buf->Command) continue; - if (*length == 0 && check2ndT2(buf, server->maxBuf) > 0) { + if (*length == 0 && check2ndT2(buf) > 0) { /* We have a multipart transact2 resp */ *is_multi_rsp = true; if (mid->resp_buf) { @@ -3130,8 +3130,7 @@ try_mount_again: 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, - (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE)); + cifs_sb->rsize = min(cifs_sb->rsize, CIFSMaxBufSize); cifs_sb->wsize = cifs_negotiate_wsize(tcon, volume_info); -- cgit v1.2.2 From e2218eab2050e879b253ca112aabd5f7167572af Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 11 Oct 2011 06:41:32 -0400 Subject: cifs: trivial: remove obsolete comment Signed-off-by: Jeff Layton Signed-off-by: Steve French --- fs/cifs/connect.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index a0077a5e0669..6126fbeaecb6 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -694,12 +694,6 @@ incomplete_rcv: * The right amount was read from socket - 4 bytes, * so we can now interpret the length field. */ - - /* - * Note that RFC 1001 length is big endian on the wire, - * but we convert it here so it is always manipulated - * as host byte order. - */ pdu_length = be32_to_cpu(smb_buffer->smb_buf_length); cFYI(1, "rfc1002 length 0x%x", pdu_length+4); -- cgit v1.2.2 From e831e6cf3acb058d898411367a582deef80e32f8 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 11 Oct 2011 06:41:32 -0400 Subject: cifs: make smb_msg local to read_from_socket If msg_controllen is 0, then the socket layer should never touch these fields. Thus, there's no need to continually reset them. Also, there's no need to keep this field on the stack for the demultiplex thread, just make it a local variable in read_from_socket. Signed-off-by: Jeff Layton Signed-off-by: Steve French --- fs/cifs/connect.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 6126fbeaecb6..ed969fd5f7cc 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -359,16 +359,20 @@ allocate_buffers(char **bigbuf, char **smallbuf, unsigned int size, } static int -read_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg, +read_from_socket(struct TCP_Server_Info *server, struct kvec *iov, unsigned int to_read, unsigned int *ptotal_read, bool is_header_read) { int length, rc = 0; unsigned int total_read; + struct msghdr smb_msg; char *buf = iov->iov_base; + smb_msg.msg_control = NULL; + smb_msg.msg_controllen = 0; + for (total_read = 0; total_read < to_read; total_read += length) { - length = kernel_recvmsg(server->ssocket, smb_msg, iov, 1, + length = kernel_recvmsg(server->ssocket, &smb_msg, iov, 1, to_read - total_read, 0); if (server->tcpStatus == CifsExiting) { /* then will exit */ @@ -397,8 +401,6 @@ read_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg, iov->iov_base = (to_read - total_read) + buf; iov->iov_len = to_read - total_read; - smb_msg->msg_control = NULL; - smb_msg->msg_controllen = 0; rc = 3; } else rc = 1; @@ -634,7 +636,6 @@ cifs_demultiplex_thread(void *p) unsigned int pdu_length, total_read; char *buf = NULL, *bigbuf = NULL, *smallbuf = NULL; struct smb_hdr *smb_buffer = NULL; - struct msghdr smb_msg; struct kvec iov; struct task_struct *task_to_wake = NULL; struct mid_q_entry *mid_entry; @@ -665,8 +666,6 @@ cifs_demultiplex_thread(void *p) buf = smallbuf; iov.iov_base = buf; iov.iov_len = 4; - smb_msg.msg_control = NULL; - smb_msg.msg_controllen = 0; pdu_length = 4; /* enough to get RFC1001 header */ incomplete_rcv: @@ -681,7 +680,7 @@ incomplete_rcv: continue; } - rc = read_from_socket(server, &smb_msg, &iov, pdu_length, + rc = read_from_socket(server, &iov, pdu_length, &total_read, true /* header read */); if (rc == 3) goto incomplete_rcv; @@ -710,7 +709,7 @@ incomplete_rcv: iov.iov_base = 4 + buf; iov.iov_len = pdu_length; - rc = read_from_socket(server, &smb_msg, &iov, pdu_length, + rc = read_from_socket(server, &iov, pdu_length, &total_read, false); if (rc == 2) break; -- cgit v1.2.2 From ba749e6d5227de22e442c6088cc7dc1f0c5c68bf Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 11 Oct 2011 06:41:32 -0400 Subject: cifs: check for unresponsive server every time we call kernel_recvmsg If the server stops sending data while in the middle of sending a response then we still want to reconnect it if it doesn't come back. Signed-off-by: Jeff Layton Signed-off-by: Steve French --- fs/cifs/connect.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index ed969fd5f7cc..abbc6c3fe3f1 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -358,6 +358,23 @@ allocate_buffers(char **bigbuf, char **smallbuf, unsigned int size, return true; } +static bool +server_unresponsive(struct TCP_Server_Info *server) +{ + if (echo_retries > 0 && server->tcpStatus == CifsGood && + time_after(jiffies, server->lstrp + + (echo_retries * SMB_ECHO_INTERVAL))) { + cERROR(1, "Server %s has not responded in %d seconds. " + "Reconnecting...", server->hostname, + (echo_retries * SMB_ECHO_INTERVAL / HZ)); + cifs_reconnect(server); + wake_up(&server->response_q); + return true; + } + + return false; +} + static int read_from_socket(struct TCP_Server_Info *server, struct kvec *iov, unsigned int to_read, @@ -372,6 +389,11 @@ read_from_socket(struct TCP_Server_Info *server, smb_msg.msg_controllen = 0; for (total_read = 0; total_read < to_read; total_read += length) { + if (server_unresponsive(server)) { + rc = 1; + break; + } + length = kernel_recvmsg(server->ssocket, &smb_msg, iov, 1, to_read - total_read, 0); if (server->tcpStatus == CifsExiting) { @@ -669,17 +691,6 @@ cifs_demultiplex_thread(void *p) pdu_length = 4; /* enough to get RFC1001 header */ incomplete_rcv: - if (echo_retries > 0 && server->tcpStatus == CifsGood && - time_after(jiffies, server->lstrp + - (echo_retries * SMB_ECHO_INTERVAL))) { - cERROR(1, "Server %s has not responded in %d seconds. " - "Reconnecting...", server->hostname, - (echo_retries * SMB_ECHO_INTERVAL / HZ)); - cifs_reconnect(server); - wake_up(&server->response_q); - continue; - } - rc = read_from_socket(server, &iov, pdu_length, &total_read, true /* header read */); if (rc == 3) -- cgit v1.2.2 From 3d3ea8e64efbeb3e4289675dbbfab82333395642 Mon Sep 17 00:00:00 2001 From: Shirish Pargaonkar Date: Mon, 26 Sep 2011 09:56:44 -0500 Subject: cifs: Add mount options for backup intent (try #6) Add mount options backupuid and backugid. It allows an authenticated user to access files with the intent to back them up including their ACLs, who may not have access permission but has "Backup files and directories user right" on them (by virtue of being part of the built-in group Backup Operators. When mount options backupuid is specified, cifs client restricts the use of backup intents to the user whose effective user id is specified along with the mount option. When mount options backupgid is specified, cifs client restricts the use of backup intents to the users whose effective user id belongs to the group id specified along with the mount option. If an authenticated user is not part of the built-in group Backup Operators at the server, access to such files is denied, even if allowed by the client. Signed-off-by: Shirish Pargaonkar Reviewed-by: Jeff Layton Signed-off-by: Steve French --- fs/cifs/connect.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index abbc6c3fe3f1..70dd2c418276 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -831,6 +831,7 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, { char *value, *data, *end; char *mountdata_copy = NULL, *options; + int err; unsigned int temp_len, i, j; char separator[2]; short int override_uid = -1; @@ -887,6 +888,8 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, cFYI(1, "Null separator not allowed"); } } + vol->backupuid_specified = false; /* no backup intent for a user */ + vol->backupgid_specified = false; /* no backup intent for a group */ while ((data = strsep(&options, separator)) != NULL) { if (!*data) @@ -1446,6 +1449,22 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, vol->mfsymlinks = true; } else if (strnicmp(data, "multiuser", 8) == 0) { vol->multiuser = true; + } else if (!strnicmp(data, "backupuid", 9) && value && *value) { + err = kstrtouint(value, 0, &vol->backupuid); + if (err < 0) { + cERROR(1, "%s: Invalid backupuid value", + __func__); + goto cifs_parse_mount_err; + } + vol->backupuid_specified = true; + } else if (!strnicmp(data, "backupgid", 9) && value && *value) { + err = kstrtouint(value, 0, &vol->backupgid); + if (err < 0) { + cERROR(1, "%s: Invalid backupgid value", + __func__); + goto cifs_parse_mount_err; + } + vol->backupgid_specified = true; } else printk(KERN_WARNING "CIFS: Unknown mount option %s\n", data); @@ -2737,6 +2756,10 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info, cifs_sb->mnt_uid = pvolume_info->linux_uid; cifs_sb->mnt_gid = pvolume_info->linux_gid; + if (pvolume_info->backupuid_specified) + cifs_sb->mnt_backupuid = pvolume_info->backupuid; + if (pvolume_info->backupgid_specified) + cifs_sb->mnt_backupgid = pvolume_info->backupgid; cifs_sb->mnt_file_mode = pvolume_info->file_mode; cifs_sb->mnt_dir_mode = pvolume_info->dir_mode; cFYI(1, "file mode: 0x%x dir mode: 0x%x", @@ -2767,6 +2790,10 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info, cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_RWPIDFORWARD; if (pvolume_info->cifs_acl) cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_ACL; + if (pvolume_info->backupuid_specified) + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_BACKUPUID; + if (pvolume_info->backupgid_specified) + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_BACKUPGID; if (pvolume_info->override_uid) cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_UID; if (pvolume_info->override_gid) -- cgit v1.2.2 From a52c1eb7ae79f0a11511d49cfc00d2f9e576abea Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 11 Oct 2011 06:41:32 -0400 Subject: cifs: simplify read_from_socket Move the iovec handling entirely into read_from_socket. That simplifies the code and gets rid of the special handling for header reads. With this we can also get rid of the "goto incomplete_rcv" label in the main demultiplex thread function since we can now treat header and non-header receives the same way. Also, make it return an int (since we'll never receive enough to worry about the sign bit anyway), and simply make it return the amount of bytes read or a negative error code. Signed-off-by: Jeff Layton Signed-off-by: Steve French --- fs/cifs/connect.c | 77 +++++++++++++++++-------------------------------------- 1 file changed, 24 insertions(+), 53 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 70dd2c418276..ff47c1842bec 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -376,35 +376,33 @@ server_unresponsive(struct TCP_Server_Info *server) } static int -read_from_socket(struct TCP_Server_Info *server, - struct kvec *iov, unsigned int to_read, - unsigned int *ptotal_read, bool is_header_read) +read_from_socket(struct TCP_Server_Info *server, char *buf, + unsigned int to_read) { - int length, rc = 0; - unsigned int total_read; + int length = 0; + int total_read; struct msghdr smb_msg; - char *buf = iov->iov_base; + struct kvec iov; smb_msg.msg_control = NULL; smb_msg.msg_controllen = 0; - for (total_read = 0; total_read < to_read; total_read += length) { + for (total_read = 0; to_read; total_read += length, to_read -= length) { if (server_unresponsive(server)) { - rc = 1; + total_read = -EAGAIN; break; } - length = kernel_recvmsg(server->ssocket, &smb_msg, iov, 1, - to_read - total_read, 0); + iov.iov_base = buf + total_read; + iov.iov_len = to_read; + length = kernel_recvmsg(server->ssocket, &smb_msg, &iov, 1, + to_read, 0); if (server->tcpStatus == CifsExiting) { - /* then will exit */ - rc = 2; + total_read = -ESHUTDOWN; break; } else if (server->tcpStatus == CifsNeedReconnect) { cifs_reconnect(server); - /* Reconnect wakes up rspns q */ - /* Now we will reread sock */ - rc = 1; + total_read = -EAGAIN; break; } else if (length == -ERESTARTSYS || length == -EAGAIN || @@ -416,28 +414,16 @@ read_from_socket(struct TCP_Server_Info *server, */ usleep_range(1000, 2000); length = 0; - if (!is_header_read) - continue; - /* Special handling for header read */ - if (total_read) { - iov->iov_base = (to_read - total_read) + - buf; - iov->iov_len = to_read - total_read; - rc = 3; - } else - rc = 1; - break; + continue; } else if (length <= 0) { - cERROR(1, "Received no data, expecting %d", - to_read - total_read); + cFYI(1, "Received no data or error: expecting %d " + "got %d", to_read, length); cifs_reconnect(server); - rc = 1; + total_read = -EAGAIN; break; } } - - *ptotal_read = total_read; - return rc; + return total_read; } static bool @@ -658,12 +644,10 @@ cifs_demultiplex_thread(void *p) unsigned int pdu_length, total_read; char *buf = NULL, *bigbuf = NULL, *smallbuf = NULL; struct smb_hdr *smb_buffer = NULL; - struct kvec iov; struct task_struct *task_to_wake = NULL; struct mid_q_entry *mid_entry; bool isLargeBuf = false; bool isMultiRsp = false; - int rc; current->flags |= PF_MEMALLOC; cFYI(1, "Demultiplex PID: %d", task_pid_nr(current)); @@ -686,19 +670,12 @@ cifs_demultiplex_thread(void *p) isMultiRsp = false; smb_buffer = (struct smb_hdr *)smallbuf; buf = smallbuf; - iov.iov_base = buf; - iov.iov_len = 4; pdu_length = 4; /* enough to get RFC1001 header */ -incomplete_rcv: - rc = read_from_socket(server, &iov, pdu_length, - &total_read, true /* header read */); - if (rc == 3) - goto incomplete_rcv; - else if (rc == 2) - break; - else if (rc == 1) + length = read_from_socket(server, buf, pdu_length); + if (length < 0) continue; + total_read = length; /* * The right amount was read from socket - 4 bytes, @@ -718,16 +695,10 @@ incomplete_rcv: buf = bigbuf; } - iov.iov_base = 4 + buf; - iov.iov_len = pdu_length; - rc = read_from_socket(server, &iov, pdu_length, - &total_read, false); - if (rc == 2) - break; - else if (rc == 1) + length = read_from_socket(server, buf + 4, pdu_length); + if (length < 0) continue; - - total_read += 4; /* account for rfc1002 hdr */ + total_read += length; dump_smb(smb_buffer, total_read); -- cgit v1.2.2 From 94443f43404239c2a6dc4252a7cb9e77f5b1eb6e Mon Sep 17 00:00:00 2001 From: Pavel Shilovsky Date: Fri, 7 Oct 2011 18:57:45 +0400 Subject: CIFS: Fix incorrect max RFC1002 write size value ..the length field has only 17 bits. Cc: Acked-by: Jeff Layton Signed-off-by: Pavel Shilovsky Signed-off-by: Steve French --- fs/cifs/connect.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index ff47c1842bec..82bc0d27e495 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2809,10 +2809,10 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info, /* * When the server doesn't allow large posix writes, only allow a wsize of - * 128k minus the size of the WRITE_AND_X header. That allows for a write up + * 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. */ -#define CIFS_MAX_RFC1002_WSIZE (128 * 1024 - sizeof(WRITE_REQ) + 4) +#define CIFS_MAX_RFC1002_WSIZE ((1<<17) - 1 - sizeof(WRITE_REQ) + 4) /* * The default wsize is 1M. find_get_pages seems to return a maximum of 256 -- cgit v1.2.2 From fe11e4ccb8479d92cd2a101d380d332544b84aaa Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 11 Oct 2011 06:41:32 -0400 Subject: cifs: clean up check_rfc1002_header Rename it for better clarity as to what it does and have the caller pass in just the single type byte. Turn the if statement into a switch and optimize it by placing the most common message type at the top. Move the header length check back into cifs_demultiplex_thread in preparation for adding a new receive phase and normalize the cFYI messages. Signed-off-by: Jeff Layton Signed-off-by: Steve French --- fs/cifs/connect.c | 63 ++++++++++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 33 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 82bc0d27e495..97a65af2a08a 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -427,29 +427,29 @@ read_from_socket(struct TCP_Server_Info *server, char *buf, } static bool -check_rfc1002_header(struct TCP_Server_Info *server, char *buf) +is_smb_response(struct TCP_Server_Info *server, unsigned char type) { - char temp = *buf; - unsigned int pdu_length = be32_to_cpu( - ((struct smb_hdr *)buf)->smb_buf_length); - /* * The first byte big endian of the length field, * is actually not part of the length but the type * with the most common, zero, as regular data. */ - if (temp == (char) RFC1002_SESSION_KEEP_ALIVE) { - return false; - } else if (temp == (char)RFC1002_POSITIVE_SESSION_RESPONSE) { - cFYI(1, "Good RFC 1002 session rsp"); - return false; - } else if (temp == (char)RFC1002_NEGATIVE_SESSION_RESPONSE) { + switch (type) { + case RFC1002_SESSION_MESSAGE: + /* Regular SMB response */ + return true; + case RFC1002_SESSION_KEEP_ALIVE: + cFYI(1, "RFC 1002 session keep alive"); + break; + case RFC1002_POSITIVE_SESSION_RESPONSE: + cFYI(1, "RFC 1002 positive session response"); + break; + case RFC1002_NEGATIVE_SESSION_RESPONSE: /* * We get this from Windows 98 instead of an error on * SMB negprot response. */ - cFYI(1, "Negative RFC1002 Session Response Error 0x%x)", - pdu_length); + cFYI(1, "RFC 1002 negative session response"); /* give server a second to clean up */ msleep(1000); /* @@ -458,29 +458,16 @@ check_rfc1002_header(struct TCP_Server_Info *server, char *buf) * is since we do not begin with RFC1001 session * initialize frame). */ - cifs_set_port((struct sockaddr *) - &server->dstaddr, CIFS_PORT); + cifs_set_port((struct sockaddr *)&server->dstaddr, CIFS_PORT); cifs_reconnect(server); wake_up(&server->response_q); - return false; - } else if (temp != (char) 0) { - cERROR(1, "Unknown RFC 1002 frame"); - cifs_dump_mem(" Received Data: ", buf, 4); - cifs_reconnect(server); - return false; - } - - /* else we have an SMB response */ - 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); + break; + default: + cERROR(1, "RFC 1002 unknown response type 0x%x", type); cifs_reconnect(server); - wake_up(&server->response_q); - return false; } - return true; + return false; } static struct mid_q_entry * @@ -683,10 +670,20 @@ cifs_demultiplex_thread(void *p) */ pdu_length = be32_to_cpu(smb_buffer->smb_buf_length); - cFYI(1, "rfc1002 length 0x%x", pdu_length+4); - if (!check_rfc1002_header(server, buf)) + cFYI(1, "RFC1002 header 0x%x", pdu_length); + if (!is_smb_response(server, buf[0])) 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); + cifs_reconnect(server); + wake_up(&server->response_q); + continue; + } + /* else length ok */ if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) { isLargeBuf = true; -- cgit v1.2.2 From 42c4dfc213190fafffc53815c2ee6064430bc379 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:28:17 -0400 Subject: cifs: turn read_from_socket into a wrapper around a vectorized version Eventually we'll want to allow cifsd to read data directly into the pagecache. In order to do that we'll need a routine that can take a kvec array and pass that directly to kernel_recvmsg. Unfortunately though, the kernel's recvmsg routines modify the kvec array that gets passed in, so we need to use a copy of the kvec array and refresh that copy on each pass through the loop. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 7 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 97a65af2a08a..4860940b748b 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -375,14 +375,54 @@ server_unresponsive(struct TCP_Server_Info *server) return false; } +/* + * 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 int -read_from_socket(struct TCP_Server_Info *server, char *buf, - unsigned int to_read) +readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, + unsigned int nr_segs, unsigned int to_read) { int length = 0; int total_read; + unsigned int segs; struct msghdr smb_msg; - struct kvec iov; + struct kvec *iov; + + iov = kmalloc(sizeof(*iov_orig) * nr_segs, GFP_NOFS); + if (!iov) + return -ENOMEM; smb_msg.msg_control = NULL; smb_msg.msg_controllen = 0; @@ -393,10 +433,11 @@ read_from_socket(struct TCP_Server_Info *server, char *buf, 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) { total_read = -ESHUTDOWN; break; @@ -423,9 +464,22 @@ read_from_socket(struct TCP_Server_Info *server, char *buf, break; } } + kfree(iov); return total_read; } +static int +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 readv_from_socket(server, &iov, 1, to_read); +} + static bool is_smb_response(struct TCP_Server_Info *server, unsigned char type) { -- cgit v1.2.2 From 1041e3f9919999b22c9c2a453aa0d92cd16b76ee Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:28:27 -0400 Subject: cifs: keep a reusable kvec array for receives Having to continually allocate a new kvec array is expensive. Allocate one that's big enough, and only reallocate it as needed. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 4860940b748b..ee70075c5fb1 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -410,6 +410,24 @@ kvec_array_init(struct kvec *new, struct kvec *iov, unsigned int nr_segs, 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; +} + static int readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, unsigned int nr_segs, unsigned int to_read) @@ -420,7 +438,7 @@ readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, struct msghdr smb_msg; struct kvec *iov; - iov = kmalloc(sizeof(*iov_orig) * nr_segs, GFP_NOFS); + iov = get_server_iovec(server, nr_segs); if (!iov) return -ENOMEM; @@ -464,7 +482,6 @@ readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, break; } } - kfree(iov); return total_read; } @@ -669,6 +686,7 @@ static void clean_demultiplex_info(struct TCP_Server_Info *server) } kfree(server->hostname); + kfree(server->iov); kfree(server); length = atomic_dec_return(&tcpSesAllocCount); -- cgit v1.2.2 From 89482a56a079f01c2f4c709f8e23fbf7eeda1b43 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:28:57 -0400 Subject: cifs: add a third receive phase to cifs_demultiplex_thread Have the demultiplex thread receive just enough to get to the MID, and then find it before receiving the rest. Later, we'll use this to swap in a preallocated receive buffer for some calls. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index ee70075c5fb1..5308bc6e1248 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -746,11 +746,25 @@ cifs_demultiplex_thread(void *p) if (!is_smb_response(server, buf[0])) 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); + wake_up(&server->response_q); + continue; + } + + /* read down to the MID */ + length = read_from_socket(server, buf + 4, + sizeof(struct smb_hdr) - 1 - 4); + if (length < 0) + continue; + total_read += length; + + 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); continue; @@ -759,12 +773,15 @@ cifs_demultiplex_thread(void *p) /* else length ok */ if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) { isLargeBuf = true; - memcpy(bigbuf, smallbuf, 4); + memcpy(bigbuf, smallbuf, total_read); smb_buffer = (struct smb_hdr *)bigbuf; buf = bigbuf; } - length = read_from_socket(server, buf + 4, pdu_length); + /* now read the rest */ + length = read_from_socket(server, + buf + sizeof(struct smb_hdr) - 1, + pdu_length - sizeof(struct smb_hdr) + 1 + 4); if (length < 0) continue; total_read += length; -- cgit v1.2.2 From ea1f4502fc939b64807f9ab0eca259321047fe83 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:29:05 -0400 Subject: cifs: move mid finding into separate routine Begin breaking up find_cifs_mid into smaller pieces. The parts that coalesce T2 responses don't really need to be done under the GlobalMid_lock anyway. Create a new function that just finds the mid on the list, and then later takes it off the list if the entire response has been received. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 113 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 47 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 5308bc6e1248..0f69b311d3fc 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -542,61 +542,80 @@ is_smb_response(struct TCP_Server_Info *server, unsigned char type) } 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); - 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; +} + +static void +dequeue_mid(struct mid_q_entry *mid, int malformed) +{ #ifdef CONFIG_CIFS_STATS2 - mid->when_received = jiffies; + mid->when_received = jiffies; #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); +} - return ret; +static struct mid_q_entry * +find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, + int *malformed, bool is_large_buf, bool *is_multi_rsp, + char **bigbuf) +{ + struct mid_q_entry *mid = NULL; + + mid = find_mid(server, buf); + if (!mid) + return mid; + + if (*malformed == 0 && check2ndT2(buf) > 0) { + /* We have a multipart transact2 resp */ + *is_multi_rsp = true; + if (mid->resp_buf) { + /* merge response - fix up 1st*/ + *malformed = coalesce_t2(buf, mid->resp_buf); + if (*malformed > 0) { + *malformed = 0; + mid->multiRsp = true; + return NULL; + } + /* 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; + } + return mid; + } + mid->resp_buf = buf; + mid->largeBuf = is_large_buf; +multi_t2_fnd: + dequeue_mid(mid, *malformed); + return mid; } static void clean_demultiplex_info(struct TCP_Server_Info *server) -- cgit v1.2.2 From ffc00e27aa5d343eb71068c185cdbd65871ccdce Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:29:13 -0400 Subject: cifs: eliminate is_multi_rsp parm to find_cifs_mid Change find_cifs_mid to only return NULL if a mid could not be found. If we got part of a multi-part T2 response, then coalesce it and still return the mid. The caller can determine the T2 receive status from the flags in the mid. With this change, there is no need to pass a pointer to "length" as well so just pass by value. If a mid is found, then we can just mark it as malformed. If one isn't found, then the value of "length" won't change anyway. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 0f69b311d3fc..8d5a61561909 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -576,8 +576,7 @@ dequeue_mid(struct mid_q_entry *mid, int malformed) static struct mid_q_entry * find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, - int *malformed, bool is_large_buf, bool *is_multi_rsp, - char **bigbuf) + int malformed, bool is_large_buf, char **bigbuf) { struct mid_q_entry *mid = NULL; @@ -585,17 +584,14 @@ find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, if (!mid) return mid; - if (*malformed == 0 && check2ndT2(buf) > 0) { - /* We have a multipart transact2 resp */ - *is_multi_rsp = true; + 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) { - *malformed = 0; - mid->multiRsp = true; - return NULL; - } + malformed = coalesce_t2(buf, mid->resp_buf); + if (malformed > 0) + return mid; + /* All parts received or packet is malformed. */ mid->multiEnd = true; goto multi_t2_fnd; @@ -614,7 +610,7 @@ find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, mid->resp_buf = buf; mid->largeBuf = is_large_buf; multi_t2_fnd: - dequeue_mid(mid, *malformed); + dequeue_mid(mid, malformed); return mid; } @@ -725,7 +721,6 @@ cifs_demultiplex_thread(void *p) struct task_struct *task_to_wake = NULL; struct mid_q_entry *mid_entry; bool isLargeBuf = false; - bool isMultiRsp = false; current->flags |= PF_MEMALLOC; cFYI(1, "Demultiplex PID: %d", task_pid_nr(current)); @@ -745,7 +740,6 @@ cifs_demultiplex_thread(void *p) continue; isLargeBuf = false; - isMultiRsp = false; smb_buffer = (struct smb_hdr *)smallbuf; buf = smallbuf; pdu_length = 4; /* enough to get RFC1001 header */ @@ -823,23 +817,25 @@ cifs_demultiplex_thread(void *p) server->lstrp = jiffies; - mid_entry = find_cifs_mid(server, smb_buffer, &length, - isLargeBuf, &isMultiRsp, &bigbuf); + mid_entry = find_cifs_mid(server, smb_buffer, length, + isLargeBuf, &bigbuf); if (mid_entry != NULL) { - mid_entry->callback(mid_entry); + if (mid_entry->multiRsp && !mid_entry->multiEnd) + continue; + /* Was previous buf put in mpx struct for multi-rsp? */ - if (!isMultiRsp) { + if (!mid_entry->multiRsp) { /* smb buffer will be freed by user thread */ if (isLargeBuf) bigbuf = NULL; else smallbuf = NULL; } + mid_entry->callback(mid_entry); } else if (length != 0) { /* response sanity checks failed */ continue; - } else if (!is_valid_oplock_break(smb_buffer, server) && - !isMultiRsp) { + } else if (!is_valid_oplock_break(smb_buffer, server)) { cERROR(1, "No task to wake, unknown frame received! " "NumMids %d", atomic_read(&midCount)); cifs_dump_mem("Received Data is: ", buf, -- cgit v1.2.2 From 2a37ef94bb153fad13cbb091aab679d7c8b9a67f Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:29:23 -0400 Subject: cifs: move buffer pointers into TCP_Server_Info We have several functions that need to access these pointers. Currently that's done with a lot of double pointer passing. Instead, move them into the TCP_Server_Info and simplify the handling. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 101 +++++++++++++++++++++++++----------------------------- 1 file changed, 46 insertions(+), 55 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 8d5a61561909..8918f935bf07 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -320,27 +320,24 @@ requeue_echo: } 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"); msleep(3000); /* retry will check if exiting */ return false; } - } else if (is_large_buf) { + } else if (server->large_buf) { /* 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"); msleep(1000); /* 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 */ } else { /* if existing small buf clear beginning */ - memset(sbuf, 0, size); + memset(server->smallbuf, 0, sizeof(struct smb_hdr)); } - *bigbuf = bbuf; - *smallbuf = sbuf; - return true; } @@ -576,7 +570,7 @@ dequeue_mid(struct mid_q_entry *mid, int malformed) static struct mid_q_entry * find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, - int malformed, bool is_large_buf, char **bigbuf) + int malformed) { struct mid_q_entry *mid = NULL; @@ -596,19 +590,27 @@ find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, mid->multiEnd = true; goto multi_t2_fnd; } - if (!is_large_buf) { + 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; - *bigbuf = NULL; + server->bigbuf = NULL; } return mid; } mid->resp_buf = buf; - mid->largeBuf = is_large_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; + } multi_t2_fnd: dequeue_mid(mid, malformed); return mid; @@ -715,12 +717,11 @@ cifs_demultiplex_thread(void *p) { int length; 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 task_struct *task_to_wake = NULL; struct mid_q_entry *mid_entry; - bool isLargeBuf = false; current->flags |= PF_MEMALLOC; cFYI(1, "Demultiplex PID: %d", task_pid_nr(current)); @@ -735,19 +736,18 @@ cifs_demultiplex_thread(void *p) if (try_to_freeze()) continue; - if (!allocate_buffers(&bigbuf, &smallbuf, - sizeof(struct smb_hdr), isLargeBuf)) + if (!allocate_buffers(server)) continue; - isLargeBuf = 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 */ length = read_from_socket(server, buf, pdu_length); if (length < 0) continue; - total_read = length; + server->total_read = length; /* * The right amount was read from socket - 4 bytes, @@ -773,7 +773,7 @@ cifs_demultiplex_thread(void *p) sizeof(struct smb_hdr) - 1 - 4); if (length < 0) continue; - total_read += length; + server->total_read += length; if (pdu_length > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) { cERROR(1, "SMB response too long (%u bytes)", @@ -785,10 +785,11 @@ cifs_demultiplex_thread(void *p) /* else length ok */ if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) { - isLargeBuf = true; - memcpy(bigbuf, smallbuf, total_read); - smb_buffer = (struct smb_hdr *)bigbuf; - buf = bigbuf; + server->large_buf = true; + memcpy(server->bigbuf, server->smallbuf, + server->total_read); + smb_buffer = (struct smb_hdr *)server->bigbuf; + buf = server->bigbuf; } /* now read the rest */ @@ -797,9 +798,9 @@ cifs_demultiplex_thread(void *p) pdu_length - sizeof(struct smb_hdr) + 1 + 4); if (length < 0) continue; - total_read += length; + server->total_read += length; - dump_smb(smb_buffer, total_read); + dump_smb(smb_buffer, server->total_read); /* * We know that we received enough to get to the MID as we @@ -810,28 +811,18 @@ cifs_demultiplex_thread(void *p) * 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); + length = checkSMB(smb_buffer, smb_buffer->Mid, + server->total_read); if (length != 0) cifs_dump_mem("Bad SMB: ", buf, - min_t(unsigned int, total_read, 48)); + min_t(unsigned int, server->total_read, 48)); server->lstrp = jiffies; - mid_entry = find_cifs_mid(server, smb_buffer, length, - isLargeBuf, &bigbuf); + mid_entry = find_cifs_mid(server, smb_buffer, length); if (mid_entry != NULL) { - if (mid_entry->multiRsp && !mid_entry->multiEnd) - continue; - - /* Was previous buf put in mpx struct for multi-rsp? */ - if (!mid_entry->multiRsp) { - /* smb buffer will be freed by user thread */ - if (isLargeBuf) - bigbuf = NULL; - else - smallbuf = NULL; - } - mid_entry->callback(mid_entry); + if (!mid_entry->multiRsp || mid_entry->multiEnd) + mid_entry->callback(mid_entry); } else if (length != 0) { /* response sanity checks failed */ continue; @@ -849,9 +840,9 @@ cifs_demultiplex_thread(void *p) } /* end while !EXITING */ /* 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); clean_demultiplex_info(server); -- cgit v1.2.2 From c8054ebdb6903208b83aa59c387b16d5129492d5 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:29:31 -0400 Subject: cifs: find mid earlier in receive codepath In order to receive directly into a preallocated buffer, we need to ID the mid earlier, before the bulk of the response is read. Call the mid finding routine as soon as we're able to read the mid. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 8918f935bf07..52195bad5e67 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -568,27 +568,21 @@ dequeue_mid(struct mid_q_entry *mid, int malformed) spin_unlock(&GlobalMid_Lock); } -static struct mid_q_entry * -find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, - int malformed) +static void +handle_mid(struct mid_q_entry *mid, struct TCP_Server_Info *server, + struct smb_hdr *buf, int malformed) { - struct mid_q_entry *mid = NULL; - - mid = find_mid(server, buf); - if (!mid) - return mid; - 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 mid; + return; /* All parts received or packet is malformed. */ mid->multiEnd = true; - goto multi_t2_fnd; + return dequeue_mid(mid, malformed); } if (!server->large_buf) { /*FIXME: switch to already allocated largebuf?*/ @@ -599,7 +593,7 @@ find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, mid->largeBuf = true; server->bigbuf = NULL; } - return mid; + return; } mid->resp_buf = buf; mid->largeBuf = server->large_buf; @@ -611,9 +605,7 @@ find_cifs_mid(struct TCP_Server_Info *server, struct smb_hdr *buf, else server->smallbuf = NULL; } -multi_t2_fnd: dequeue_mid(mid, malformed); - return mid; } static void clean_demultiplex_info(struct TCP_Server_Info *server) @@ -775,6 +767,8 @@ cifs_demultiplex_thread(void *p) continue; server->total_read += length; + mid_entry = find_mid(server, smb_buffer); + if (pdu_length > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) { cERROR(1, "SMB response too long (%u bytes)", pdu_length); @@ -819,8 +813,8 @@ cifs_demultiplex_thread(void *p) server->lstrp = jiffies; - mid_entry = find_cifs_mid(server, smb_buffer, length); if (mid_entry != NULL) { + handle_mid(mid_entry, server, smb_buffer, length); if (!mid_entry->multiRsp || mid_entry->multiEnd) mid_entry->callback(mid_entry); } else if (length != 0) { -- cgit v1.2.2 From e9097ab48978c89b9c0926e2ae5d49bf6ea91b18 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:29:40 -0400 Subject: cifs: break out 3rd receive phase into separate function Move the entire 3rd phase of the receive codepath into a separate function in preparation for the addition of a pluggable receive function. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 101 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 42 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 52195bad5e67..f05dedda37c6 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -704,6 +704,61 @@ static void clean_demultiplex_info(struct TCP_Server_Info *server) 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 = 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 cifs_demultiplex_thread(void *p) { @@ -769,57 +824,19 @@ cifs_demultiplex_thread(void *p) mid_entry = find_mid(server, smb_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); + length = standard_receive3(server, mid_entry); + if (length < 0) continue; - } - /* else length ok */ - if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) { - server->large_buf = true; - memcpy(server->bigbuf, server->smallbuf, - server->total_read); - smb_buffer = (struct smb_hdr *)server->bigbuf; + if (server->large_buf) { buf = server->bigbuf; + smb_buffer = (struct smb_hdr *)buf; } - /* now read the rest */ - length = read_from_socket(server, - buf + sizeof(struct smb_hdr) - 1, - pdu_length - sizeof(struct smb_hdr) + 1 + 4); - if (length < 0) - continue; - 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)); - server->lstrp = jiffies; - if (mid_entry != NULL) { - handle_mid(mid_entry, server, smb_buffer, length); if (!mid_entry->multiRsp || mid_entry->multiEnd) mid_entry->callback(mid_entry); - } else if (length != 0) { - /* response sanity checks failed */ - continue; } else if (!is_valid_oplock_break(smb_buffer, server)) { cERROR(1, "No task to wake, unknown frame received! " "NumMids %d", atomic_read(&midCount)); -- cgit v1.2.2 From 44d22d846fdc7c3e688fc1ff5ae6d06d08bb5656 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:29:49 -0400 Subject: cifs: add a callback function to receive the rest of the frame In order to handle larger SMBs for readpages and other calls, we want to be able to read into a preallocated set of buffers. Rather than changing all of the existing code to preallocate buffers however, we instead add a receive callback function to the MID. cifsd will call this function once the mid_q_entry has been identified in order to receive the rest of the SMB. If the mid can't be identified or the receive pointer is unset, then the standard 3rd phase receive function will be called. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index f05dedda37c6..eeee2f5d13ce 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -824,7 +824,11 @@ cifs_demultiplex_thread(void *p) mid_entry = find_mid(server, smb_buffer); - length = standard_receive3(server, mid_entry); + if (!mid_entry || !mid_entry->receive) + length = standard_receive3(server, mid_entry); + else + length = mid_entry->receive(server, mid_entry); + if (length < 0) continue; -- cgit v1.2.2 From e28bc5b1fdbd6e850488234d6072e6b66fc46146 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:30:07 -0400 Subject: cifs: add cifs_async_readv ...which will allow cifs to do an asynchronous read call to the server. The caller will allocate and set up cifs_readdata for each READ_AND_X call that should be issued on the wire. The pages passed in are added to the pagecache, but not placed on the LRU list yet (as we need the page->lru to keep the pages on the list in the readdata). When cifsd identifies the mid, it will see that there is a special receive handler for the call, and use that to receive the rest of the frame. cifs_readv_receive will then marshal up a kvec array with kmapped pages from the pagecache, which eliminates one copy of the data. Once the data is received, the pages are added to the LRU list, set uptodate, and unlocked. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index eeee2f5d13ce..3d518b9e8c18 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -422,9 +422,9 @@ get_server_iovec(struct TCP_Server_Info *server, unsigned int nr_segs) return new_iov; } -static int -readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, - unsigned int nr_segs, unsigned int to_read) +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 total_read; @@ -479,16 +479,16 @@ readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, return total_read; } -static int -read_from_socket(struct TCP_Server_Info *server, char *buf, - unsigned int to_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 readv_from_socket(server, &iov, 1, to_read); + return cifs_readv_from_socket(server, &iov, 1, to_read); } static bool @@ -553,8 +553,8 @@ find_mid(struct TCP_Server_Info *server, struct smb_hdr *buf) return NULL; } -static void -dequeue_mid(struct mid_q_entry *mid, int malformed) +void +dequeue_mid(struct mid_q_entry *mid, bool malformed) { #ifdef CONFIG_CIFS_STATS2 mid->when_received = jiffies; @@ -730,7 +730,7 @@ standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid) } /* now read the rest */ - length = read_from_socket(server, + length = cifs_read_from_socket(server, buf + sizeof(struct smb_hdr) - 1, pdu_length - sizeof(struct smb_hdr) + 1 + 4); if (length < 0) @@ -791,7 +791,7 @@ cifs_demultiplex_thread(void *p) buf = server->smallbuf; 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) continue; server->total_read = length; @@ -816,8 +816,8 @@ cifs_demultiplex_thread(void *p) } /* read down to the MID */ - length = read_from_socket(server, buf + 4, - sizeof(struct smb_hdr) - 1 - 4); + length = cifs_read_from_socket(server, buf + 4, + sizeof(struct smb_hdr) - 1 - 4); if (length < 0) continue; server->total_read += length; -- cgit v1.2.2 From 5eba8ab3606621f7e175ae9f521d71f3ac534f82 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:30:26 -0400 Subject: cifs: allow for larger rsize= options and change defaults Currently we cap the rsize at a value that fits in CIFSMaxBufSize. That's not needed any longer for readpages. Allow the use of larger values for readpages. cifs_iovec_read and cifs_read however are still limited to the CIFSMaxBufSize. Make sure they don't exceed that. The patch also changes the rsize defaults. The default when unix extensions are enabled is set to 1M for parity with the wsize, and there is a hard cap of ~16M. When unix extensions are not enabled, the default is set to 60k. According to MS-CIFS, Windows servers can only send a max of 60k at a time, so this is more efficient than requesting a larger size. If the user wishes however, the max can be extended up to 128k - the length of the READ_RSP header. Really old servers however require a special hack to ensure that we don't request too large a read. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 120 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 47 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 3d518b9e8c18..06dfaacfea5d 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2310,16 +2310,16 @@ compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data) (new->mnt_cifs_flags & CIFS_MOUNT_MASK)) 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) return 0; + if (new->rsize && new->rsize < old->rsize) + return 0; + if (old->mnt_uid != new->mnt_uid || old->mnt_gid != new->mnt_gid) return 0; @@ -2757,14 +2757,6 @@ void reset_cifs_unix_caps(int xid, struct cifs_tcon *tcon, 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); #ifdef CONFIG_CIFS_DEBUG2 if (cap & CIFS_UNIX_FCNTL_CAP) @@ -2809,27 +2801,11 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info, spin_lock_init(&cifs_sb->tlink_tree_lock); 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->mnt_uid = pvolume_info->linux_uid; @@ -2904,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 * 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. + * + * 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_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_RSIZE ((1<<17) - 1 - sizeof(READ_RSP) + 4) /* * 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 * 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 cifs_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info) @@ -2934,7 +2922,7 @@ cifs_negotiate_wsize(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 wsize = pvolume_info->wsize ? pvolume_info->wsize : - CIFS_DEFAULT_WSIZE; + CIFS_DEFAULT_IOSIZE; /* can server support 24-bit write sizes? (via UNIX extensions) */ if (!tcon->unix_ext || !(unix_cap & CIFS_UNIX_LARGE_WRITE_CAP)) @@ -2957,6 +2945,50 @@ cifs_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info) 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 is_path_accessible(int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const char *full_path) @@ -3234,14 +3266,8 @@ try_mount_again: 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->rsize = cifs_negotiate_rsize(tcon, volume_info); remote_path_check: #ifdef CONFIG_CIFS_DFS_UPCALL -- cgit v1.2.2 From 66bfaadc3da74fecb4ba8b03f6b81d5f58b031fa Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 19 Oct 2011 15:30:35 -0400 Subject: cifs: tune bdi.ra_pages in accordance with the rsize Tune bdi.ra_pages to be a multiple of the rsize. This prevents the VFS from asking for pages that require small reads to satisfy. Reviewed-and-Tested-by: Pavel Shilovsky Signed-off-by: Jeff Layton --- fs/cifs/connect.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'fs/cifs/connect.c') diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 06dfaacfea5d..f70d87d6ba61 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3182,6 +3182,22 @@ cifs_get_volume_info(char *mount_data, const char *devname) 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 cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info) { @@ -3200,8 +3216,6 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info) if (rc) return rc; - cifs_sb->bdi.ra_pages = default_backing_dev_info.ra_pages; - #ifdef CONFIG_CIFS_DFS_UPCALL try_mount_again: /* cleanup activities if we're chasing a referral */ @@ -3269,6 +3283,9 @@ try_mount_again: 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: #ifdef CONFIG_CIFS_DFS_UPCALL /* -- cgit v1.2.2