cifs: when server doesn't set CAP_LARGE_READ_X, cap default rsize at MaxBufferSize
[pandora-kernel.git] / fs / cifs / connect.c
index f31dc9a..0ae86dd 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *   fs/cifs/connect.c
  *
- *   Copyright (C) International Business Machines  Corp., 2002,2009
+ *   Copyright (C) International Business Machines  Corp., 2002,2011
  *   Author(s): Steve French (sfrench@us.ibm.com)
  *
  *   This library is free software; you can redistribute it and/or modify
@@ -102,7 +102,7 @@ enum {
        Opt_srcaddr, Opt_prefixpath,
        Opt_iocharset, Opt_sockopt,
        Opt_netbiosname, Opt_servern,
-       Opt_ver, Opt_sec,
+       Opt_ver, Opt_vers, Opt_sec, Opt_cache,
 
        /* Mount options to be ignored */
        Opt_ignore,
@@ -164,7 +164,8 @@ static const match_table_t cifs_mount_option_tokens = {
        { Opt_sign, "sign" },
        { Opt_seal, "seal" },
        { Opt_direct, "direct" },
-       { Opt_direct, "forceddirectio" },
+       { Opt_direct, "directio" },
+       { Opt_direct, "forcedirectio" },
        { Opt_strictcache, "strictcache" },
        { Opt_noac, "noac" },
        { Opt_fsc, "fsc" },
@@ -209,12 +210,14 @@ static const match_table_t cifs_mount_option_tokens = {
        { Opt_netbiosname, "netbiosname=%s" },
        { Opt_servern, "servern=%s" },
        { Opt_ver, "ver=%s" },
-       { Opt_ver, "vers=%s" },
-       { Opt_ver, "version=%s" },
+       { Opt_vers, "vers=%s" },
        { Opt_sec, "sec=%s" },
+       { Opt_cache, "cache=%s" },
 
        { Opt_ignore, "cred" },
        { Opt_ignore, "credentials" },
+       { Opt_ignore, "cred=%s" },
+       { Opt_ignore, "credentials=%s" },
        { Opt_ignore, "guest" },
        { Opt_ignore, "rw" },
        { Opt_ignore, "ro" },
@@ -258,6 +261,26 @@ static const match_table_t cifs_secflavor_tokens = {
        { Opt_sec_err, NULL }
 };
 
+/* cache flavors */
+enum {
+       Opt_cache_loose,
+       Opt_cache_strict,
+       Opt_cache_none,
+       Opt_cache_err
+};
+
+static const match_table_t cifs_cacheflavor_tokens = {
+       { Opt_cache_loose, "loose" },
+       { Opt_cache_strict, "strict" },
+       { Opt_cache_none, "none" },
+       { Opt_cache_err, NULL }
+};
+
+static const match_table_t cifs_smb_version_tokens = {
+       { Smb_1, SMB1_VERSION_STRING },
+       { Smb_21, SMB21_VERSION_STRING },
+};
+
 static int ip_connect(struct TCP_Server_Info *server);
 static int generic_ip_connect(struct TCP_Server_Info *server);
 static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);
@@ -546,7 +569,7 @@ allocate_buffers(struct TCP_Server_Info *server)
                }
        } else if (server->large_buf) {
                /* we are reusing a dirty large buf, clear its start */
-               memset(server->bigbuf, 0, header_size());
+               memset(server->bigbuf, 0, HEADER_SIZE(server));
        }
 
        if (!server->smallbuf) {
@@ -560,7 +583,7 @@ allocate_buffers(struct TCP_Server_Info *server)
                /* beginning of smb buffer is cleared in our buf_get */
        } else {
                /* if existing small buf clear beginning */
-               memset(server->smallbuf, 0, header_size());
+               memset(server->smallbuf, 0, HEADER_SIZE(server));
        }
 
        return true;
@@ -761,25 +784,6 @@ is_smb_response(struct TCP_Server_Info *server, unsigned char type)
        return false;
 }
 
-static struct mid_q_entry *
-find_mid(struct TCP_Server_Info *server, char *buffer)
-{
-       struct smb_hdr *buf = (struct smb_hdr *)buffer;
-       struct mid_q_entry *mid;
-
-       spin_lock(&GlobalMid_Lock);
-       list_for_each_entry(mid, &server->pending_mid_q, qhead) {
-               if (mid->mid == buf->Mid &&
-                   mid->mid_state == MID_REQUEST_SUBMITTED &&
-                   le16_to_cpu(mid->command) == buf->Command) {
-                       spin_unlock(&GlobalMid_Lock);
-                       return mid;
-               }
-       }
-       spin_unlock(&GlobalMid_Lock);
-       return NULL;
-}
-
 void
 dequeue_mid(struct mid_q_entry *mid, bool malformed)
 {
@@ -931,7 +935,7 @@ standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid)
        unsigned int pdu_length = get_rfc1002_length(buf);
 
        /* make sure this will fit in a large buffer */
-       if (pdu_length > CIFSMaxBufSize + max_header_size() - 4) {
+       if (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server) - 4) {
                cERROR(1, "SMB response too long (%u bytes)",
                        pdu_length);
                cifs_reconnect(server);
@@ -947,8 +951,8 @@ standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid)
        }
 
        /* now read the rest */
-       length = cifs_read_from_socket(server, buf + header_size() - 1,
-                                      pdu_length - header_size() + 1 + 4);
+       length = cifs_read_from_socket(server, buf + HEADER_SIZE(server) - 1,
+                               pdu_length - HEADER_SIZE(server) + 1 + 4);
        if (length < 0)
                return length;
        server->total_read += length;
@@ -964,7 +968,7 @@ standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid)
         * 48 bytes is enough to display the header and a little bit
         * into the payload for debugging purposes.
         */
-       length = checkSMB(buf, server->total_read);
+       length = server->ops->check_message(buf, server->total_read);
        if (length != 0)
                cifs_dump_mem("Bad SMB: ", buf,
                        min_t(unsigned int, server->total_read, 48));
@@ -1022,7 +1026,7 @@ cifs_demultiplex_thread(void *p)
                        continue;
 
                /* make sure we have enough to get to the MID */
-               if (pdu_length < header_size() - 1 - 4) {
+               if (pdu_length < HEADER_SIZE(server) - 1 - 4) {
                        cERROR(1, "SMB response too short (%u bytes)",
                                pdu_length);
                        cifs_reconnect(server);
@@ -1032,12 +1036,12 @@ cifs_demultiplex_thread(void *p)
 
                /* read down to the MID */
                length = cifs_read_from_socket(server, buf + 4,
-                                              header_size() - 1 - 4);
+                                              HEADER_SIZE(server) - 1 - 4);
                if (length < 0)
                        continue;
                server->total_read += length;
 
-               mid_entry = find_mid(server, buf);
+               mid_entry = server->ops->find_mid(server, buf);
 
                if (!mid_entry || !mid_entry->receive)
                        length = standard_receive3(server, mid_entry);
@@ -1054,12 +1058,15 @@ cifs_demultiplex_thread(void *p)
                if (mid_entry != NULL) {
                        if (!mid_entry->multiRsp || mid_entry->multiEnd)
                                mid_entry->callback(mid_entry);
-               } else if (!is_valid_oplock_break(buf, server)) {
+               } else if (!server->ops->is_oplock_break ||
+                          !server->ops->is_oplock_break(buf, server)) {
                        cERROR(1, "No task to wake, unknown frame received! "
                                   "NumMids %d", atomic_read(&midCount));
-                       cifs_dump_mem("Received Data is: ", buf, header_size());
+                       cifs_dump_mem("Received Data is: ", buf,
+                                     HEADER_SIZE(server));
 #ifdef CONFIG_CIFS_DEBUG2
-                       cifs_dump_detail(buf);
+                       if (server->ops->dump_detail)
+                               server->ops->dump_detail(buf);
                        cifs_dump_mids(server);
 #endif /* CIFS_DEBUG2 */
 
@@ -1182,6 +1189,54 @@ static int cifs_parse_security_flavors(char *value,
        return 0;
 }
 
+static int
+cifs_parse_cache_flavor(char *value, struct smb_vol *vol)
+{
+       substring_t args[MAX_OPT_ARGS];
+
+       switch (match_token(value, cifs_cacheflavor_tokens, args)) {
+       case Opt_cache_loose:
+               vol->direct_io = false;
+               vol->strict_io = false;
+               break;
+       case Opt_cache_strict:
+               vol->direct_io = false;
+               vol->strict_io = true;
+               break;
+       case Opt_cache_none:
+               vol->direct_io = true;
+               vol->strict_io = false;
+               break;
+       default:
+               cERROR(1, "bad cache= option: %s", value);
+               return 1;
+       }
+       return 0;
+}
+
+static int
+cifs_parse_smb_version(char *value, struct smb_vol *vol)
+{
+       substring_t args[MAX_OPT_ARGS];
+
+       switch (match_token(value, cifs_smb_version_tokens, args)) {
+       case Smb_1:
+               vol->ops = &smb1_operations;
+               vol->vals = &smb1_values;
+               break;
+#ifdef CONFIG_CIFS_SMB2
+       case Smb_21:
+               vol->ops = &smb21_operations;
+               vol->vals = &smb21_values;
+               break;
+#endif
+       default:
+               cERROR(1, "Unknown vers= option specified: %s", value);
+               return 1;
+       }
+       return 0;
+}
+
 static int
 cifs_parse_mount_options(const char *mountdata, const char *devname,
                         struct smb_vol *vol)
@@ -1200,6 +1255,8 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
        char *string = NULL;
        char *tmp_end, *value;
        char delim;
+       bool cache_specified = false;
+       static bool cache_warned = false;
 
        separator[0] = ',';
        separator[1] = 0;
@@ -1233,6 +1290,10 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
 
        vol->actimeo = CIFS_DEF_ACTIMEO;
 
+       /* FIXME: add autonegotiation -- for now, SMB1 is default */
+       vol->ops = &smb1_operations;
+       vol->vals = &smb1_values;
+
        if (!mountdata)
                goto cifs_parse_mount_err;
 
@@ -1411,10 +1472,20 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
                        vol->seal = 1;
                        break;
                case Opt_direct:
-                       vol->direct_io = 1;
+                       cache_specified = true;
+                       vol->direct_io = true;
+                       vol->strict_io = false;
+                       cERROR(1, "The \"directio\" option will be removed in "
+                                 "3.7. Please switch to the \"cache=none\" "
+                                 "option.");
                        break;
                case Opt_strictcache:
-                       vol->strict_io = 1;
+                       cache_specified = true;
+                       vol->direct_io = false;
+                       vol->strict_io = true;
+                       cERROR(1, "The \"strictcache\" option will be removed "
+                               "in 3.7. Please switch to the \"cache=strict\" "
+                               "option.");
                        break;
                case Opt_noac:
                        printk(KERN_WARNING "CIFS: Mount option noac not "
@@ -1582,24 +1653,26 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
                         * If yes, we have encountered a double deliminator
                         * reset the NULL character to the deliminator
                         */
-                       if (tmp_end < end && tmp_end[1] == delim)
+                       if (tmp_end < end && tmp_end[1] == delim) {
                                tmp_end[0] = delim;
 
-                       /* Keep iterating until we get to a single deliminator
-                        * OR the end
-                        */
-                       while ((tmp_end = strchr(tmp_end, delim)) != NULL &&
-                              (tmp_end[1] == delim)) {
-                               tmp_end = (char *) &tmp_end[2];
-                       }
+                               /* Keep iterating until we get to a single
+                                * deliminator OR the end
+                                */
+                               while ((tmp_end = strchr(tmp_end, delim))
+                                       != NULL && (tmp_end[1] == delim)) {
+                                               tmp_end = (char *) &tmp_end[2];
+                               }
 
-                       /* Reset var options to point to next element */
-                       if (tmp_end) {
-                               tmp_end[0] = '\0';
-                               options = (char *) &tmp_end[1];
-                       } else
-                               /* Reached the end of the mount option string */
-                               options = end;
+                               /* Reset var options to point to next element */
+                               if (tmp_end) {
+                                       tmp_end[0] = '\0';
+                                       options = (char *) &tmp_end[1];
+                               } else
+                                       /* Reached the end of the mount option
+                                        * string */
+                                       options = end;
+                       }
 
                        /* Now build new password string */
                        temp_len = strlen(value);
@@ -1818,8 +1891,7 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
                        if (string == NULL)
                                goto out_nomem;
 
-                       if (strnicmp(string, "cifs", 4) == 0 ||
-                           strnicmp(string, "1", 1) == 0) {
+                       if (strnicmp(string, "1", 1) == 0) {
                                /* This is the default */
                                break;
                        }
@@ -1827,6 +1899,14 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
                        printk(KERN_WARNING "CIFS: Invalid version"
                                            " specified\n");
                        goto cifs_parse_mount_err;
+               case Opt_vers:
+                       string = match_strdup(args);
+                       if (string == NULL)
+                               goto out_nomem;
+
+                       if (cifs_parse_smb_version(string, vol) != 0)
+                               goto cifs_parse_mount_err;
+                       break;
                case Opt_sec:
                        string = match_strdup(args);
                        if (string == NULL)
@@ -1835,6 +1915,15 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
                        if (cifs_parse_security_flavors(string, vol) != 0)
                                goto cifs_parse_mount_err;
                        break;
+               case Opt_cache:
+                       cache_specified = true;
+                       string = match_strdup(args);
+                       if (string == NULL)
+                               goto out_nomem;
+
+                       if (cifs_parse_cache_flavor(string, vol) != 0)
+                               goto cifs_parse_mount_err;
+                       break;
                default:
                        /*
                         * An option we don't recognize. Save it off for later
@@ -1878,6 +1967,14 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
                printk(KERN_NOTICE "CIFS: ignoring forcegid mount option "
                                   "specified with no gid= option.\n");
 
+       /* FIXME: remove this block in 3.7 */
+       if (!cache_specified && !cache_warned) {
+               cache_warned = true;
+               printk(KERN_NOTICE "CIFS: no cache= option specified, using "
+                                  "\"cache=loose\". This default will change "
+                                  "to \"cache=strict\" in 3.7.\n");
+       }
+
        kfree(mountdata_copy);
        return 0;
 
@@ -2038,6 +2135,9 @@ match_security(struct TCP_Server_Info *server, struct smb_vol *vol)
 static int match_server(struct TCP_Server_Info *server, struct sockaddr *addr,
                         struct smb_vol *vol)
 {
+       if ((server->vals != vol->vals) || (server->ops != vol->ops))
+               return 0;
+
        if (!net_eq(cifs_net_ns(server), current->nsproxy->net_ns))
                return 0;
 
@@ -2160,6 +2260,8 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
                goto out_err;
        }
 
+       tcp_ses->ops = volume_info->ops;
+       tcp_ses->vals = volume_info->vals;
        cifs_set_net_ns(tcp_ses, get_net(current->nsproxy->net_ns));
        tcp_ses->hostname = extract_hostname(volume_info->UNC);
        if (IS_ERR(tcp_ses->hostname)) {
@@ -2183,6 +2285,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
        tcp_ses->session_estab = false;
        tcp_ses->sequence_number = 0;
        tcp_ses->lstrp = jiffies;
+       spin_lock_init(&tcp_ses->req_lock);
        INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
        INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
        INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
@@ -3228,10 +3331,6 @@ 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%hx  dir mode: 0x%hx",
@@ -3262,10 +3361,14 @@ 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)
+       if (pvolume_info->backupuid_specified) {
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_BACKUPUID;
-       if (pvolume_info->backupgid_specified)
+               cifs_sb->mnt_backupuid = pvolume_info->backupuid;
+       }
+       if (pvolume_info->backupgid_specified) {
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_BACKUPGID;
+               cifs_sb->mnt_backupgid = pvolume_info->backupgid;
+       }
        if (pvolume_info->override_uid)
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_UID;
        if (pvolume_info->override_gid)
@@ -3392,18 +3495,15 @@ cifs_negotiate_rsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info)
         * 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.
+        * OS/2 just sends back short reads.
         *
-        * 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 the server doesn't advertise CAP_LARGE_READ_X, then assume that
+        * it can't handle a read request larger than its MaxBufferSize either.
         */
        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);
 
@@ -3565,6 +3665,7 @@ cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data,
        if (cifs_parse_mount_options(mount_data, devname, volume_info))
                return -EINVAL;
 
+
        if (volume_info->nullauth) {
                cFYI(1, "Anonymous login");
                kfree(volume_info->username);
@@ -3614,22 +3715,6 @@ 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)
 {
@@ -3717,7 +3802,7 @@ try_mount_again:
        cifs_sb->rsize = cifs_negotiate_rsize(tcon, volume_info);
 
        /* tune readahead according to rsize */
-       cifs_sb->bdi.ra_pages = cifs_ra_pages(cifs_sb);
+       cifs_sb->bdi.ra_pages = cifs_sb->rsize / PAGE_CACHE_SIZE;
 
 remote_path_check:
 #ifdef CONFIG_CIFS_DFS_UPCALL
@@ -3854,7 +3939,7 @@ CIFSTCon(unsigned int xid, struct cifs_ses *ses,
        header_assemble(smb_buffer, SMB_COM_TREE_CONNECT_ANDX,
                        NULL /*no tid */ , 4 /*wct */ );
 
-       smb_buffer->Mid = GetNextMid(ses->server);
+       smb_buffer->Mid = get_next_mid(ses->server);
        smb_buffer->Uid = ses->Suid;
        pSMB = (TCONX_REQ *) smb_buffer;
        pSMBr = (TCONX_RSP *) smb_buffer_response;
@@ -4022,11 +4107,11 @@ int cifs_negotiate_protocol(unsigned int xid, struct cifs_ses *ses)
        if (server->maxBuf != 0)
                return 0;
 
-       cifs_set_credits(server, 1);
+       set_credits(server, 1);
        rc = CIFSSMBNegotiate(xid, ses);
        if (rc == -EAGAIN) {
                /* retry only once on 1st time connection */
-               cifs_set_credits(server, 1);
+               set_credits(server, 1);
                rc = CIFSSMBNegotiate(xid, ses);
                if (rc == -EAGAIN)
                        rc = -EHOSTDOWN;