sanitize cifs_umount() prototype
[pandora-kernel.git] / fs / cifs / connect.c
index 277262a..b270222 100644 (file)
 
 extern mempool_t *cifs_req_poolp;
 
-struct smb_vol {
-       char *username;
-       char *password;
-       char *domainname;
-       char *UNC;
-       char *UNCip;
-       char *iocharset;  /* local code page for mapping to and from Unicode */
-       char source_rfc1001_name[RFC1001_NAME_LEN_WITH_NULL]; /* clnt nb name */
-       char target_rfc1001_name[RFC1001_NAME_LEN_WITH_NULL]; /* srvr nb name */
-       uid_t cred_uid;
-       uid_t linux_uid;
-       gid_t linux_gid;
-       mode_t file_mode;
-       mode_t dir_mode;
-       unsigned secFlg;
-       bool retry:1;
-       bool intr:1;
-       bool setuids:1;
-       bool override_uid:1;
-       bool override_gid:1;
-       bool dynperm:1;
-       bool noperm:1;
-       bool no_psx_acl:1; /* set if posix acl support should be disabled */
-       bool cifs_acl:1;
-       bool no_xattr:1;   /* set if xattr (EA) support should be disabled*/
-       bool server_ino:1; /* use inode numbers from server ie UniqueId */
-       bool direct_io:1;
-       bool strict_io:1; /* strict cache behavior */
-       bool remap:1;      /* set to remap seven reserved chars in filenames */
-       bool posix_paths:1; /* unset to not ask for posix pathnames. */
-       bool no_linux_ext:1;
-       bool sfu_emul:1;
-       bool nullauth:1;   /* attempt to authenticate with null user */
-       bool nocase:1;     /* request case insensitive filenames */
-       bool nobrl:1;      /* disable sending byte range locks to srv */
-       bool mand_lock:1;  /* send mandatory not posix byte range lock reqs */
-       bool seal:1;       /* request transport encryption on share */
-       bool nodfs:1;      /* Do not request DFS, even if available */
-       bool local_lease:1; /* check leases only on local system, not remote */
-       bool noblocksnd:1;
-       bool noautotune:1;
-       bool nostrictsync:1; /* do not force expensive SMBflush on every sync */
-       bool fsc:1;     /* enable fscache */
-       bool mfsymlinks:1; /* use Minshall+French Symlinks */
-       bool multiuser:1;
-       unsigned int rsize;
-       unsigned int wsize;
-       bool sockopt_tcp_nodelay:1;
-       unsigned short int port;
-       unsigned long actimeo; /* attribute cache timeout (jiffies) */
-       char *prepath;
-       struct sockaddr_storage srcaddr; /* allow binding to a local IP */
-       struct nls_table *local_nls;
-};
-
 /* FIXME: should these be tunable? */
 #define TLINK_ERROR_EXPIRE     (1 * HZ)
 #define TLINK_IDLE_EXPIRE      (600 * HZ)
@@ -134,9 +79,10 @@ cifs_reconnect(struct TCP_Server_Info *server)
 {
        int rc = 0;
        struct list_head *tmp, *tmp2;
-       struct cifsSesInfo *ses;
-       struct cifsTconInfo *tcon;
+       struct cifs_ses *ses;
+       struct cifs_tcon *tcon;
        struct mid_q_entry *mid_entry;
+       struct list_head retry_list;
 
        spin_lock(&GlobalMid_Lock);
        if (server->tcpStatus == CifsExiting) {
@@ -156,11 +102,11 @@ cifs_reconnect(struct TCP_Server_Info *server)
        cFYI(1, "%s: marking sessions and tcons for reconnect", __func__);
        spin_lock(&cifs_tcp_ses_lock);
        list_for_each(tmp, &server->smb_ses_list) {
-               ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list);
+               ses = list_entry(tmp, struct cifs_ses, smb_ses_list);
                ses->need_reconnect = true;
                ses->ipc_tid = 0;
                list_for_each(tmp2, &ses->tcon_list) {
-                       tcon = list_entry(tmp2, struct cifsTconInfo, tcon_list);
+                       tcon = list_entry(tmp2, struct cifs_tcon, tcon_list);
                        tcon->need_reconnect = true;
                }
        }
@@ -188,18 +134,25 @@ cifs_reconnect(struct TCP_Server_Info *server)
        mutex_unlock(&server->srv_mutex);
 
        /* mark submitted MIDs for retry and issue callback */
-       cFYI(1, "%s: issuing mid callbacks", __func__);
+       INIT_LIST_HEAD(&retry_list);
+       cFYI(1, "%s: moving mids to private list", __func__);
        spin_lock(&GlobalMid_Lock);
        list_for_each_safe(tmp, tmp2, &server->pending_mid_q) {
                mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
                if (mid_entry->midState == MID_REQUEST_SUBMITTED)
                        mid_entry->midState = MID_RETRY_NEEDED;
+               list_move(&mid_entry->qhead, &retry_list);
+       }
+       spin_unlock(&GlobalMid_Lock);
+
+       cFYI(1, "%s: issuing mid callbacks", __func__);
+       list_for_each_safe(tmp, tmp2, &retry_list) {
+               mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
                list_del_init(&mid_entry->qhead);
                mid_entry->callback(mid_entry);
        }
-       spin_unlock(&GlobalMid_Lock);
 
-       while (server->tcpStatus == CifsNeedReconnect) {
+       do {
                try_to_freeze();
 
                /* we should try only the port we connected to before */
@@ -214,7 +167,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
                                server->tcpStatus = CifsNeedNegotiate;
                        spin_unlock(&GlobalMid_Lock);
                }
-       }
+       } while (server->tcpStatus == CifsNeedReconnect);
 
        return rc;
 }
@@ -316,19 +269,19 @@ static int coalesce_t2(struct smb_hdr *psecond, struct smb_hdr *pTargetSMB)
        put_unaligned_le16(total_in_buf, &pSMBt->t2_rsp.DataCount);
 
        /* fix up the BCC */
-       byte_count = get_bcc_le(pTargetSMB);
+       byte_count = get_bcc(pTargetSMB);
        byte_count += total_in_buf2;
        /* is the result too big for the field? */
        if (byte_count > USHRT_MAX)
                return -EPROTO;
-       put_bcc_le(byte_count, pTargetSMB);
+       put_bcc(byte_count, pTargetSMB);
 
-       byte_count = pTargetSMB->smb_buf_length;
+       byte_count = be32_to_cpu(pTargetSMB->smb_buf_length);
        byte_count += total_in_buf2;
        /* don't allow buffer to overflow */
        if (byte_count > CIFSMaxBufSize)
                return -ENOBUFS;
-       pTargetSMB->smb_buf_length = byte_count;
+       pTargetSMB->smb_buf_length = cpu_to_be32(byte_count);
 
        memcpy(data_area_of_target, data_area_of_buf2, total_in_buf2);
 
@@ -495,8 +448,7 @@ incomplete_rcv:
                /* Note that FC 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((__force __be32)smb_buffer->smb_buf_length);
-               smb_buffer->smb_buf_length = pdu_length;
+               pdu_length = be32_to_cpu(smb_buffer->smb_buf_length);
 
                cFYI(1, "rfc1002 length 0x%x", pdu_length+4);
 
@@ -672,12 +624,12 @@ multi_t2_fnd:
                        mid_entry->when_received = jiffies;
 #endif
                        list_del_init(&mid_entry->qhead);
-                       mid_entry->callback(mid_entry);
                        break;
                }
                spin_unlock(&GlobalMid_Lock);
 
                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 */
@@ -735,21 +687,31 @@ multi_t2_fnd:
                sock_release(csocket);
                server->ssocket = NULL;
        }
-       /* buffer usuallly 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);
 
        if (!list_empty(&server->pending_mid_q)) {
+               struct list_head dispose_list;
+
+               INIT_LIST_HEAD(&dispose_list);
                spin_lock(&GlobalMid_Lock);
                list_for_each_safe(tmp, tmp2, &server->pending_mid_q) {
                        mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
-                       cFYI(1, "Clearing Mid 0x%x - issuing callback",
-                                        mid_entry->mid);
+                       cFYI(1, "Clearing mid 0x%x", mid_entry->mid);
+                       mid_entry->midState = MID_SHUTDOWN;
+                       list_move(&mid_entry->qhead, &dispose_list);
+               }
+               spin_unlock(&GlobalMid_Lock);
+
+               /* now walk dispose list and issue callbacks */
+               list_for_each_safe(tmp, tmp2, &dispose_list) {
+                       mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
+                       cFYI(1, "Callback mid 0x%x", mid_entry->mid);
                        list_del_init(&mid_entry->qhead);
                        mid_entry->callback(mid_entry);
                }
-               spin_unlock(&GlobalMid_Lock);
                /* 1/8th of sec is more than enough time for them to exit */
                msleep(125);
        }
@@ -818,10 +780,11 @@ extract_hostname(const char *unc)
 }
 
 static int
-cifs_parse_mount_options(char *options, const char *devname,
+cifs_parse_mount_options(const char *mountdata, const char *devname,
                         struct smb_vol *vol)
 {
        char *value, *data, *end;
+       char *mountdata_copy = NULL, *options;
        unsigned int  temp_len, i, j;
        char separator[2];
        short int override_uid = -1;
@@ -861,9 +824,14 @@ cifs_parse_mount_options(char *options, const char *devname,
 
        vol->actimeo = CIFS_DEF_ACTIMEO;
 
-       if (!options)
-               return 1;
+       if (!mountdata)
+               goto cifs_parse_mount_err;
+
+       mountdata_copy = kstrndup(mountdata, PAGE_SIZE, GFP_KERNEL);
+       if (!mountdata_copy)
+               goto cifs_parse_mount_err;
 
+       options = mountdata_copy;
        end = options + strlen(options);
        if (strncmp(options, "sep=", 4) == 0) {
                if (options[4] != 0) {
@@ -889,17 +857,22 @@ cifs_parse_mount_options(char *options, const char *devname,
                        if (!value) {
                                printk(KERN_WARNING
                                       "CIFS: invalid or missing username\n");
-                               return 1;       /* needs_arg; */
+                               goto cifs_parse_mount_err;
                        } else if (!*value) {
                                /* null user, ie anonymous, authentication */
                                vol->nullauth = 1;
                        }
                        if (strnlen(value, MAX_USERNAME_SIZE) <
                                                MAX_USERNAME_SIZE) {
-                               vol->username = value;
+                               vol->username = kstrdup(value, GFP_KERNEL);
+                               if (!vol->username) {
+                                       printk(KERN_WARNING "CIFS: no memory "
+                                                           "for username\n");
+                                       goto cifs_parse_mount_err;
+                               }
                        } else {
                                printk(KERN_WARNING "CIFS: username too long\n");
-                               return 1;
+                               goto cifs_parse_mount_err;
                        }
                } else if (strnicmp(data, "pass", 4) == 0) {
                        if (!value) {
@@ -963,7 +936,7 @@ cifs_parse_mount_options(char *options, const char *devname,
                                if (vol->password == NULL) {
                                        printk(KERN_WARNING "CIFS: no memory "
                                                            "for password\n");
-                                       return 1;
+                                       goto cifs_parse_mount_err;
                                }
                                for (i = 0, j = 0; i < temp_len; i++, j++) {
                                        vol->password[j] = value[i];
@@ -979,7 +952,7 @@ cifs_parse_mount_options(char *options, const char *devname,
                                if (vol->password == NULL) {
                                        printk(KERN_WARNING "CIFS: no memory "
                                                            "for password\n");
-                                       return 1;
+                                       goto cifs_parse_mount_err;
                                }
                                strcpy(vol->password, value);
                        }
@@ -989,11 +962,16 @@ cifs_parse_mount_options(char *options, const char *devname,
                                vol->UNCip = NULL;
                        } else if (strnlen(value, INET6_ADDRSTRLEN) <
                                                        INET6_ADDRSTRLEN) {
-                               vol->UNCip = value;
+                               vol->UNCip = kstrdup(value, GFP_KERNEL);
+                               if (!vol->UNCip) {
+                                       printk(KERN_WARNING "CIFS: no memory "
+                                                           "for UNC IP\n");
+                                       goto cifs_parse_mount_err;
+                               }
                        } else {
                                printk(KERN_WARNING "CIFS: ip address "
                                                    "too long\n");
-                               return 1;
+                               goto cifs_parse_mount_err;
                        }
                } else if (strnicmp(data, "sec", 3) == 0) {
                        if (!value || !*value) {
@@ -1006,7 +984,7 @@ cifs_parse_mount_options(char *options, const char *devname,
                                /* vol->secFlg |= CIFSSEC_MUST_SEAL |
                                        CIFSSEC_MAY_KRB5; */
                                cERROR(1, "Krb5 cifs privacy not supported");
-                               return 1;
+                               goto cifs_parse_mount_err;
                        } else if (strnicmp(value, "krb5", 4) == 0) {
                                vol->secFlg |= CIFSSEC_MAY_KRB5;
                        } else if (strnicmp(value, "ntlmsspi", 8) == 0) {
@@ -1036,7 +1014,16 @@ cifs_parse_mount_options(char *options, const char *devname,
                                vol->nullauth = 1;
                        } else {
                                cERROR(1, "bad security option: %s", value);
-                               return 1;
+                               goto cifs_parse_mount_err;
+                       }
+               } else if (strnicmp(data, "vers", 3) == 0) {
+                       if (!value || !*value) {
+                               cERROR(1, "no protocol version specified"
+                                         " after vers= mount option");
+                       } else if ((strnicmp(value, "cifs", 4) == 0) ||
+                                  (strnicmp(value, "1", 1) == 0)) {
+                               /* this is the default */
+                               continue;
                        }
                } else if ((strnicmp(data, "unc", 3) == 0)
                           || (strnicmp(data, "target", 6) == 0)
@@ -1044,12 +1031,12 @@ cifs_parse_mount_options(char *options, const char *devname,
                        if (!value || !*value) {
                                printk(KERN_WARNING "CIFS: invalid path to "
                                                    "network resource\n");
-                               return 1;       /* needs_arg; */
+                               goto cifs_parse_mount_err;
                        }
                        if ((temp_len = strnlen(value, 300)) < 300) {
                                vol->UNC = kmalloc(temp_len+1, GFP_KERNEL);
                                if (vol->UNC == NULL)
-                                       return 1;
+                                       goto cifs_parse_mount_err;
                                strcpy(vol->UNC, value);
                                if (strncmp(vol->UNC, "//", 2) == 0) {
                                        vol->UNC[0] = '\\';
@@ -1058,27 +1045,32 @@ cifs_parse_mount_options(char *options, const char *devname,
                                        printk(KERN_WARNING
                                               "CIFS: UNC Path does not begin "
                                               "with // or \\\\ \n");
-                                       return 1;
+                                       goto cifs_parse_mount_err;
                                }
                        } else {
                                printk(KERN_WARNING "CIFS: UNC name too long\n");
-                               return 1;
+                               goto cifs_parse_mount_err;
                        }
                } else if ((strnicmp(data, "domain", 3) == 0)
                           || (strnicmp(data, "workgroup", 5) == 0)) {
                        if (!value || !*value) {
                                printk(KERN_WARNING "CIFS: invalid domain name\n");
-                               return 1;       /* needs_arg; */
+                               goto cifs_parse_mount_err;
                        }
                        /* BB are there cases in which a comma can be valid in
                        a domain name and need special handling? */
                        if (strnlen(value, 256) < 256) {
-                               vol->domainname = value;
+                               vol->domainname = kstrdup(value, GFP_KERNEL);
+                               if (!vol->domainname) {
+                                       printk(KERN_WARNING "CIFS: no memory "
+                                                           "for domainname\n");
+                                       goto cifs_parse_mount_err;
+                               }
                                cFYI(1, "Domain name set");
                        } else {
                                printk(KERN_WARNING "CIFS: domain name too "
                                                    "long\n");
-                               return 1;
+                               goto cifs_parse_mount_err;
                        }
                } else if (strnicmp(data, "srcaddr", 7) == 0) {
                        vol->srcaddr.ss_family = AF_UNSPEC;
@@ -1086,7 +1078,7 @@ cifs_parse_mount_options(char *options, const char *devname,
                        if (!value || !*value) {
                                printk(KERN_WARNING "CIFS: srcaddr value"
                                       " not specified.\n");
-                               return 1;       /* needs_arg; */
+                               goto cifs_parse_mount_err;
                        }
                        i = cifs_convert_address((struct sockaddr *)&vol->srcaddr,
                                                 value, strlen(value));
@@ -1094,20 +1086,20 @@ cifs_parse_mount_options(char *options, const char *devname,
                                printk(KERN_WARNING "CIFS:  Could not parse"
                                       " srcaddr: %s\n",
                                       value);
-                               return 1;
+                               goto cifs_parse_mount_err;
                        }
                } else if (strnicmp(data, "prefixpath", 10) == 0) {
                        if (!value || !*value) {
                                printk(KERN_WARNING
                                        "CIFS: invalid path prefix\n");
-                               return 1;       /* needs_argument */
+                               goto cifs_parse_mount_err;
                        }
                        if ((temp_len = strnlen(value, 1024)) < 1024) {
                                if (value[0] != '/')
                                        temp_len++;  /* missing leading slash */
                                vol->prepath = kmalloc(temp_len+1, GFP_KERNEL);
                                if (vol->prepath == NULL)
-                                       return 1;
+                                       goto cifs_parse_mount_err;
                                if (value[0] != '/') {
                                        vol->prepath[0] = '/';
                                        strcpy(vol->prepath+1, value);
@@ -1116,24 +1108,33 @@ cifs_parse_mount_options(char *options, const char *devname,
                                cFYI(1, "prefix path %s", vol->prepath);
                        } else {
                                printk(KERN_WARNING "CIFS: prefix too long\n");
-                               return 1;
+                               goto cifs_parse_mount_err;
                        }
                } else if (strnicmp(data, "iocharset", 9) == 0) {
                        if (!value || !*value) {
                                printk(KERN_WARNING "CIFS: invalid iocharset "
                                                    "specified\n");
-                               return 1;       /* needs_arg; */
+                               goto cifs_parse_mount_err;
                        }
                        if (strnlen(value, 65) < 65) {
-                               if (strnicmp(value, "default", 7))
-                                       vol->iocharset = value;
+                               if (strnicmp(value, "default", 7)) {
+                                       vol->iocharset = kstrdup(value,
+                                                                GFP_KERNEL);
+
+                                       if (!vol->iocharset) {
+                                               printk(KERN_WARNING "CIFS: no "
+                                                                  "memory for"
+                                                                  "charset\n");
+                                               goto cifs_parse_mount_err;
+                                       }
+                               }
                                /* if iocharset not set then load_nls_default
                                   is used by caller */
                                cFYI(1, "iocharset set to %s", value);
                        } else {
                                printk(KERN_WARNING "CIFS: iocharset name "
                                                    "too long.\n");
-                               return 1;
+                               goto cifs_parse_mount_err;
                        }
                } else if (!strnicmp(data, "uid", 3) && value && *value) {
                        vol->linux_uid = simple_strtoul(value, &value, 0);
@@ -1246,7 +1247,7 @@ cifs_parse_mount_options(char *options, const char *devname,
                                if (vol->actimeo > CIFS_MAX_ACTIMEO) {
                                        cERROR(1, "CIFS: attribute cache"
                                                        "timeout too large");
-                                       return 1;
+                                       goto cifs_parse_mount_err;
                                }
                        }
                } else if (strnicmp(data, "credentials", 4) == 0) {
@@ -1358,6 +1359,8 @@ cifs_parse_mount_options(char *options, const char *devname,
                        vol->server_ino = 1;
                } else if (strnicmp(data, "noserverino", 9) == 0) {
                        vol->server_ino = 0;
+               } else if (strnicmp(data, "rwpidforward", 4) == 0) {
+                       vol->rwpidforward = 1;
                } else if (strnicmp(data, "cifsacl", 7) == 0) {
                        vol->cifs_acl = 1;
                } else if (strnicmp(data, "nocifsacl", 9) == 0) {
@@ -1388,9 +1391,9 @@ cifs_parse_mount_options(char *options, const char *devname,
                                "/proc/fs/cifs/LookupCacheEnabled to 0\n");
                } else if (strnicmp(data, "fsc", 3) == 0) {
 #ifndef CONFIG_CIFS_FSCACHE
-                       cERROR(1, "FS-Cache support needs CONFIG_CIFS_FSCACHE"
+                       cERROR(1, "FS-Cache support needs CONFIG_CIFS_FSCACHE "
                                  "kernel config option set");
-                       return 1;
+                       goto cifs_parse_mount_err;
 #endif
                        vol->fsc = true;
                } else if (strnicmp(data, "mfsymlinks", 10) == 0) {
@@ -1405,12 +1408,12 @@ cifs_parse_mount_options(char *options, const char *devname,
                if (devname == NULL) {
                        printk(KERN_WARNING "CIFS: Missing UNC name for mount "
                                                "target\n");
-                       return 1;
+                       goto cifs_parse_mount_err;
                }
                if ((temp_len = strnlen(devname, 300)) < 300) {
                        vol->UNC = kmalloc(temp_len+1, GFP_KERNEL);
                        if (vol->UNC == NULL)
-                               return 1;
+                               goto cifs_parse_mount_err;
                        strcpy(vol->UNC, devname);
                        if (strncmp(vol->UNC, "//", 2) == 0) {
                                vol->UNC[0] = '\\';
@@ -1418,21 +1421,21 @@ cifs_parse_mount_options(char *options, const char *devname,
                        } else if (strncmp(vol->UNC, "\\\\", 2) != 0) {
                                printk(KERN_WARNING "CIFS: UNC Path does not "
                                                    "begin with // or \\\\ \n");
-                               return 1;
+                               goto cifs_parse_mount_err;
                        }
                        value = strpbrk(vol->UNC+2, "/\\");
                        if (value)
                                *value = '\\';
                } else {
                        printk(KERN_WARNING "CIFS: UNC name too long\n");
-                       return 1;
+                       goto cifs_parse_mount_err;
                }
        }
 
        if (vol->multiuser && !(vol->secFlg & CIFSSEC_MAY_KRB5)) {
                cERROR(1, "Multiuser mounts currently require krb5 "
                          "authentication!");
-               return 1;
+               goto cifs_parse_mount_err;
        }
 
        if (vol->UNCip == NULL)
@@ -1450,7 +1453,12 @@ cifs_parse_mount_options(char *options, const char *devname,
                printk(KERN_NOTICE "CIFS: ignoring forcegid mount option "
                                   "specified with no gid= option.\n");
 
+       kfree(mountdata_copy);
        return 0;
+
+cifs_parse_mount_err:
+       kfree(mountdata_copy);
+       return 1;
 }
 
 /** Returns true if srcaddr isn't specified and rhs isn't
@@ -1589,16 +1597,35 @@ match_security(struct TCP_Server_Info *server, struct smb_vol *vol)
 
        /* now check if signing mode is acceptable */
        if ((secFlags & CIFSSEC_MAY_SIGN) == 0 &&
-           (server->secMode & SECMODE_SIGN_REQUIRED))
+           (server->sec_mode & SECMODE_SIGN_REQUIRED))
                        return false;
        else if (((secFlags & CIFSSEC_MUST_SIGN) == CIFSSEC_MUST_SIGN) &&
-                (server->secMode &
+                (server->sec_mode &
                  (SECMODE_SIGN_ENABLED|SECMODE_SIGN_REQUIRED)) == 0)
                        return false;
 
        return true;
 }
 
+static int match_server(struct TCP_Server_Info *server, struct sockaddr *addr,
+                        struct smb_vol *vol)
+{
+       if (!net_eq(cifs_net_ns(server), current->nsproxy->net_ns))
+               return 0;
+
+       if (!match_address(server, addr,
+                          (struct sockaddr *)&vol->srcaddr))
+               return 0;
+
+       if (!match_port(server, addr))
+               return 0;
+
+       if (!match_security(server, vol))
+               return 0;
+
+       return 1;
+}
+
 static struct TCP_Server_Info *
 cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol)
 {
@@ -1606,17 +1633,7 @@ cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol)
 
        spin_lock(&cifs_tcp_ses_lock);
        list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
-               if (!net_eq(cifs_net_ns(server), current->nsproxy->net_ns))
-                       continue;
-
-               if (!match_address(server, addr,
-                                  (struct sockaddr *)&vol->srcaddr))
-                       continue;
-
-               if (!match_port(server, addr))
-                       continue;
-
-               if (!match_security(server, vol))
+               if (!match_server(server, addr, vol))
                        continue;
 
                ++server->srv_count;
@@ -1810,32 +1827,39 @@ out_err:
        return ERR_PTR(rc);
 }
 
-static struct cifsSesInfo *
+static int match_session(struct cifs_ses *ses, struct smb_vol *vol)
+{
+       switch (ses->server->secType) {
+       case Kerberos:
+               if (vol->cred_uid != ses->cred_uid)
+                       return 0;
+               break;
+       default:
+               /* anything else takes username/password */
+               if (ses->user_name == NULL)
+                       return 0;
+               if (strncmp(ses->user_name, vol->username,
+                           MAX_USERNAME_SIZE))
+                       return 0;
+               if (strlen(vol->username) != 0 &&
+                   ses->password != NULL &&
+                   strncmp(ses->password,
+                           vol->password ? vol->password : "",
+                           MAX_PASSWORD_SIZE))
+                       return 0;
+       }
+       return 1;
+}
+
+static struct cifs_ses *
 cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol)
 {
-       struct cifsSesInfo *ses;
+       struct cifs_ses *ses;
 
        spin_lock(&cifs_tcp_ses_lock);
        list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
-               switch (server->secType) {
-               case Kerberos:
-                       if (vol->cred_uid != ses->cred_uid)
-                               continue;
-                       break;
-               default:
-                       /* anything else takes username/password */
-                       if (ses->user_name == NULL)
-                               continue;
-                       if (strncmp(ses->user_name, vol->username,
-                                   MAX_USERNAME_SIZE))
-                               continue;
-                       if (strlen(vol->username) != 0 &&
-                           ses->password != NULL &&
-                           strncmp(ses->password,
-                                   vol->password ? vol->password : "",
-                                   MAX_PASSWORD_SIZE))
-                               continue;
-               }
+               if (!match_session(ses, vol))
+                       continue;
                ++ses->ses_count;
                spin_unlock(&cifs_tcp_ses_lock);
                return ses;
@@ -1845,7 +1869,7 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol)
 }
 
 static void
-cifs_put_smb_ses(struct cifsSesInfo *ses)
+cifs_put_smb_ses(struct cifs_ses *ses)
 {
        int xid;
        struct TCP_Server_Info *server = ses->server;
@@ -1871,11 +1895,11 @@ cifs_put_smb_ses(struct cifsSesInfo *ses)
 
 static bool warned_on_ntlm;  /* globals init to false automatically */
 
-static struct cifsSesInfo *
+static struct cifs_ses *
 cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
 {
        int rc = -ENOMEM, xid;
-       struct cifsSesInfo *ses;
+       struct cifs_ses *ses;
        struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr;
        struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr;
 
@@ -1952,7 +1976,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
                warned_on_ntlm = true;
                cERROR(1, "default security mechanism requested.  The default "
                        "security mechanism will be upgraded from ntlm to "
-                       "ntlmv2 in kernel release 2.6.41");
+                       "ntlmv2 in kernel release 3.1");
        }
        ses->overrideSecFlg = volume_info->secFlg;
 
@@ -1978,20 +2002,26 @@ get_ses_fail:
        return ERR_PTR(rc);
 }
 
-static struct cifsTconInfo *
-cifs_find_tcon(struct cifsSesInfo *ses, const char *unc)
+static int match_tcon(struct cifs_tcon *tcon, const char *unc)
+{
+       if (tcon->tidStatus == CifsExiting)
+               return 0;
+       if (strncmp(tcon->treeName, unc, MAX_TREE_SIZE))
+               return 0;
+       return 1;
+}
+
+static struct cifs_tcon *
+cifs_find_tcon(struct cifs_ses *ses, const char *unc)
 {
        struct list_head *tmp;
-       struct cifsTconInfo *tcon;
+       struct cifs_tcon *tcon;
 
        spin_lock(&cifs_tcp_ses_lock);
        list_for_each(tmp, &ses->tcon_list) {
-               tcon = list_entry(tmp, struct cifsTconInfo, tcon_list);
-               if (tcon->tidStatus == CifsExiting)
-                       continue;
-               if (strncmp(tcon->treeName, unc, MAX_TREE_SIZE))
+               tcon = list_entry(tmp, struct cifs_tcon, tcon_list);
+               if (!match_tcon(tcon, unc))
                        continue;
-
                ++tcon->tc_count;
                spin_unlock(&cifs_tcp_ses_lock);
                return tcon;
@@ -2001,10 +2031,10 @@ cifs_find_tcon(struct cifsSesInfo *ses, const char *unc)
 }
 
 static void
-cifs_put_tcon(struct cifsTconInfo *tcon)
+cifs_put_tcon(struct cifs_tcon *tcon)
 {
        int xid;
-       struct cifsSesInfo *ses = tcon->ses;
+       struct cifs_ses *ses = tcon->ses;
 
        cFYI(1, "%s: tc_count=%d\n", __func__, tcon->tc_count);
        spin_lock(&cifs_tcp_ses_lock);
@@ -2025,11 +2055,11 @@ cifs_put_tcon(struct cifsTconInfo *tcon)
        cifs_put_smb_ses(ses);
 }
 
-static struct cifsTconInfo *
-cifs_get_tcon(struct cifsSesInfo *ses, struct smb_vol *volume_info)
+static struct cifs_tcon *
+cifs_get_tcon(struct cifs_ses *ses, struct smb_vol *volume_info)
 {
        int rc, xid;
-       struct cifsTconInfo *tcon;
+       struct cifs_tcon *tcon;
 
        tcon = cifs_find_tcon(ses, volume_info->UNC);
        if (tcon) {
@@ -2118,8 +2148,105 @@ cifs_put_tlink(struct tcon_link *tlink)
        return;
 }
 
+static inline struct tcon_link *
+cifs_sb_master_tlink(struct cifs_sb_info *cifs_sb)
+{
+       return cifs_sb->master_tlink;
+}
+
+static int
+compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data)
+{
+       struct cifs_sb_info *old = CIFS_SB(sb);
+       struct cifs_sb_info *new = mnt_data->cifs_sb;
+
+       if ((sb->s_flags & CIFS_MS_MASK) != (mnt_data->flags & CIFS_MS_MASK))
+               return 0;
+
+       if ((old->mnt_cifs_flags & CIFS_MOUNT_MASK) !=
+           (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.
+        */
+       if (new->wsize && new->wsize < old->wsize)
+               return 0;
+
+       if (old->mnt_uid != new->mnt_uid || old->mnt_gid != new->mnt_gid)
+               return 0;
+
+       if (old->mnt_file_mode != new->mnt_file_mode ||
+           old->mnt_dir_mode != new->mnt_dir_mode)
+               return 0;
+
+       if (strcmp(old->local_nls->charset, new->local_nls->charset))
+               return 0;
+
+       if (old->actimeo != new->actimeo)
+               return 0;
+
+       return 1;
+}
+
 int
-get_dfs_path(int xid, struct cifsSesInfo *pSesInfo, const char *old_path,
+cifs_match_super(struct super_block *sb, void *data)
+{
+       struct cifs_mnt_data *mnt_data = (struct cifs_mnt_data *)data;
+       struct smb_vol *volume_info;
+       struct cifs_sb_info *cifs_sb;
+       struct TCP_Server_Info *tcp_srv;
+       struct cifs_ses *ses;
+       struct cifs_tcon *tcon;
+       struct tcon_link *tlink;
+       struct sockaddr_storage addr;
+       int rc = 0;
+
+       memset(&addr, 0, sizeof(struct sockaddr_storage));
+
+       spin_lock(&cifs_tcp_ses_lock);
+       cifs_sb = CIFS_SB(sb);
+       tlink = cifs_get_tlink(cifs_sb_master_tlink(cifs_sb));
+       if (IS_ERR(tlink)) {
+               spin_unlock(&cifs_tcp_ses_lock);
+               return rc;
+       }
+       tcon = tlink_tcon(tlink);
+       ses = tcon->ses;
+       tcp_srv = ses->server;
+
+       volume_info = mnt_data->vol;
+
+       if (!volume_info->UNCip || !volume_info->UNC)
+               goto out;
+
+       rc = cifs_fill_sockaddr((struct sockaddr *)&addr,
+                               volume_info->UNCip,
+                               strlen(volume_info->UNCip),
+                               volume_info->port);
+       if (!rc)
+               goto out;
+
+       if (!match_server(tcp_srv, (struct sockaddr *)&addr, volume_info) ||
+           !match_session(ses, volume_info) ||
+           !match_tcon(tcon, volume_info->UNC)) {
+               rc = 0;
+               goto out;
+       }
+
+       rc = compare_mount_options(sb, mnt_data);
+out:
+       cifs_put_tlink(tlink);
+       spin_unlock(&cifs_tcp_ses_lock);
+       return rc;
+}
+
+int
+get_dfs_path(int xid, struct cifs_ses *pSesInfo, const char *old_path,
             const struct nls_table *nls_codepage, unsigned int *pnum_referrals,
             struct dfs_info3_param **preferrals, int remap)
 {
@@ -2280,7 +2407,7 @@ ip_rfc1001_connect(struct TCP_Server_Info *server)
                smb_buf = (struct smb_hdr *)ses_init_buf;
 
                /* sizeof RFC1002_SESSION_REQUEST with no scope */
-               smb_buf->smb_buf_length = 0x81000044;
+               smb_buf->smb_buf_length = cpu_to_be32(0x81000044);
                rc = smb_send(server, smb_buf, 0x44);
                kfree(ses_init_buf);
                /*
@@ -2418,8 +2545,8 @@ ip_connect(struct TCP_Server_Info *server)
        return generic_ip_connect(server);
 }
 
-void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon,
-                         struct super_block *sb, struct smb_vol *vol_info)
+void reset_cifs_unix_caps(int xid, struct cifs_tcon *tcon,
+                         struct cifs_sb_info *cifs_sb, struct smb_vol *vol_info)
 {
        /* if we are reconnecting then should we check to see if
         * any requested capabilities changed locally e.g. via
@@ -2447,7 +2574,7 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon,
 
        if (!CIFSSMBQFSUnixInfo(xid, tcon)) {
                __u64 cap = le64_to_cpu(tcon->fsUnixInfo.Capability);
-
+               cFYI(1, "unix caps which server supports %lld", cap);
                /* check for reconnect case in which we do not
                   want to change the mount behavior if we can avoid it */
                if (vol_info == NULL) {
@@ -2465,33 +2592,31 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon,
                        }
                }
 
+               if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)
+                       cERROR(1, "per-share encryption not supported yet");
+
                cap &= CIFS_UNIX_CAP_MASK;
                if (vol_info && vol_info->no_psx_acl)
                        cap &= ~CIFS_UNIX_POSIX_ACL_CAP;
                else if (CIFS_UNIX_POSIX_ACL_CAP & cap) {
                        cFYI(1, "negotiated posix acl support");
-                       if (sb)
-                               sb->s_flags |= MS_POSIXACL;
+                       if (cifs_sb)
+                               cifs_sb->mnt_cifs_flags |=
+                                       CIFS_MOUNT_POSIXACL;
                }
 
                if (vol_info && vol_info->posix_paths == 0)
                        cap &= ~CIFS_UNIX_POSIX_PATHNAMES_CAP;
                else if (cap & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
                        cFYI(1, "negotiate posix pathnames");
-                       if (sb)
-                               CIFS_SB(sb)->mnt_cifs_flags |=
+                       if (cifs_sb)
+                               cifs_sb->mnt_cifs_flags |=
                                        CIFS_MOUNT_POSIX_PATHS;
                }
 
-               /* We might be setting the path sep back to a different
-               form if we are reconnecting and the server switched its
-               posix path capability for this share */
-               if (sb && (CIFS_SB(sb)->prepathlen > 0))
-                       CIFS_SB(sb)->prepath[0] = CIFS_DIR_SEP(CIFS_SB(sb));
-
-               if (sb && (CIFS_SB(sb)->rsize > 127 * 1024)) {
+               if (cifs_sb && (cifs_sb->rsize > 127 * 1024)) {
                        if ((cap & CIFS_UNIX_LARGE_READ_CAP) == 0) {
-                               CIFS_SB(sb)->rsize = 127 * 1024;
+                               cifs_sb->rsize = 127 * 1024;
                                cFYI(DBG2, "larger reads not supported by srv");
                        }
                }
@@ -2513,6 +2638,10 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon,
                        cFYI(1, "very large read cap");
                if (cap & CIFS_UNIX_LARGE_WRITE_CAP)
                        cFYI(1, "very large write cap");
+               if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP)
+                       cFYI(1, "transport encryption cap");
+               if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)
+                       cFYI(1, "mandatory transport encryption cap");
 #endif /* CIFS_DEBUG2 */
                if (CIFSSMBSetFSUnixInfo(xid, tcon, cap)) {
                        if (vol_info == NULL) {
@@ -2529,31 +2658,14 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon,
        }
 }
 
-static void
-convert_delimiter(char *path, char delim)
-{
-       int i;
-       char old_delim;
-
-       if (path == NULL)
-               return;
-
-       if (delim == '/')
-               old_delim = '\\';
-       else
-               old_delim = '/';
-
-       for (i = 0; path[i] != '\0'; i++) {
-               if (path[i] == old_delim)
-                       path[i] = delim;
-       }
-}
-
-static void setup_cifs_sb(struct smb_vol *pvolume_info,
-                         struct cifs_sb_info *cifs_sb)
+void cifs_setup_cifs_sb(struct smb_vol *pvolume_info,
+                       struct cifs_sb_info *cifs_sb)
 {
        INIT_DELAYED_WORK(&cifs_sb->prune_tlinks, cifs_prune_tlinks);
 
+       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);
@@ -2564,40 +2676,19 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
        else /* default */
                cifs_sb->rsize = CIFSMaxBufSize;
 
-       if (pvolume_info->wsize > PAGEVEC_SIZE * PAGE_CACHE_SIZE) {
-               cERROR(1, "wsize %d too large, using 4096 instead",
-                         pvolume_info->wsize);
-               cifs_sb->wsize = 4096;
-       } else if (pvolume_info->wsize)
-               cifs_sb->wsize = pvolume_info->wsize;
-       else
-               cifs_sb->wsize = min_t(const int,
-                                       PAGEVEC_SIZE * PAGE_CACHE_SIZE,
-                                       127*1024);
-               /* old default of CIFSMaxBufSize was too small now
-                  that SMB Write2 can send multiple pages in kvec.
-                  RFC1001 does not describe what happens when frame
-                  bigger than 128K is sent so use that as max in
-                  conjunction with 52K kvec constraint on arch with 4K
-                  page size  */
-
        if (cifs_sb->rsize < 2048) {
                cifs_sb->rsize = 2048;
                /* Windows ME may prefer this */
                cFYI(1, "readsize set to minimum: 2048");
        }
-       /* calculate prepath */
-       cifs_sb->prepath = pvolume_info->prepath;
-       if (cifs_sb->prepath) {
-               cifs_sb->prepathlen = strlen(cifs_sb->prepath);
-               /* we can not convert the / to \ in the path
-               separators in the prefixpath yet because we do not
-               know (until reset_cifs_unix_caps is called later)
-               whether POSIX PATH CAP is available. We normalize
-               the / to \ after reset_cifs_unix_caps is called */
-               pvolume_info->prepath = NULL;
-       } else
-               cifs_sb->prepathlen = 0;
+
+       /*
+        * Temporarily set wsize for matching superblock. If we end up using
+        * new sb then cifs_negotiate_wsize will later negotiate it downward
+        * if needed.
+        */
+       cifs_sb->wsize = pvolume_info->wsize;
+
        cifs_sb->mnt_uid = pvolume_info->linux_uid;
        cifs_sb->mnt_gid = pvolume_info->linux_gid;
        cifs_sb->mnt_file_mode = pvolume_info->file_mode;
@@ -2606,6 +2697,7 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
                cifs_sb->mnt_file_mode, cifs_sb->mnt_dir_mode);
 
        cifs_sb->actimeo = pvolume_info->actimeo;
+       cifs_sb->local_nls = pvolume_info->local_nls;
 
        if (pvolume_info->noperm)
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_PERM;
@@ -2625,6 +2717,8 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NOSSYNC;
        if (pvolume_info->mand_lock)
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NOPOSIXBRL;
+       if (pvolume_info->rwpidforward)
+               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->override_uid)
@@ -2658,8 +2752,55 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
                           "mount option supported");
 }
 
+/*
+ * When the server supports very large writes via POSIX extensions, we can
+ * allow up to 2^24 - PAGE_CACHE_SIZE.
+ *
+ * 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.
+ */
+#define CIFS_MAX_WSIZE ((1<<24) - PAGE_CACHE_SIZE)
+
+/*
+ * When the server doesn't allow large posix writes, default to a wsize of
+ * 128k - PAGE_CACHE_SIZE -- one page less than the largest frame size
+ * described in RFC1001. This allows space for the header without going over
+ * that by default.
+ */
+#define CIFS_MAX_RFC1001_WSIZE (128 * 1024 - PAGE_CACHE_SIZE)
+
+/*
+ * 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)
+
+static unsigned int
+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;
+
+       /* can server support 24-bit write sizes? (via UNIX extensions) */
+       if (!tcon->unix_ext || !(unix_cap & CIFS_UNIX_LARGE_WRITE_CAP))
+               wsize = min_t(unsigned int, wsize, CIFS_MAX_RFC1001_WSIZE);
+
+       /* no CAP_LARGE_WRITE_X? Limit it to 16 bits */
+       if (!(server->capabilities & CAP_LARGE_WRITE_X))
+               wsize = min_t(unsigned int, wsize, USHRT_MAX);
+
+       /* hard limit of CIFS_MAX_WSIZE */
+       wsize = min_t(unsigned int, wsize, CIFS_MAX_WSIZE);
+
+       return wsize;
+}
+
 static int
-is_path_accessible(int xid, struct cifsTconInfo *tcon,
+is_path_accessible(int xid, struct cifs_tcon *tcon,
                   struct cifs_sb_info *cifs_sb, const char *full_path)
 {
        int rc;
@@ -2682,8 +2823,8 @@ is_path_accessible(int xid, struct cifsTconInfo *tcon,
        return rc;
 }
 
-static void
-cleanup_volume_info(struct smb_vol **pvolume_info)
+void
+cifs_cleanup_volume_info(struct smb_vol **pvolume_info)
 {
        struct smb_vol *volume_info;
 
@@ -2691,8 +2832,12 @@ cleanup_volume_info(struct smb_vol **pvolume_info)
                return;
 
        volume_info = *pvolume_info;
+       kfree(volume_info->username);
        kzfree(volume_info->password);
        kfree(volume_info->UNC);
+       kfree(volume_info->UNCip);
+       kfree(volume_info->domainname);
+       kfree(volume_info->iocharset);
        kfree(volume_info->prepath);
        kfree(volume_info);
        *pvolume_info = NULL;
@@ -2709,55 +2854,78 @@ build_unc_path_to_root(const struct smb_vol *volume_info,
        char *full_path;
 
        int unc_len = strnlen(volume_info->UNC, MAX_TREE_SIZE + 1);
-       full_path = kmalloc(unc_len + cifs_sb->prepathlen + 1, GFP_KERNEL);
+       full_path = kmalloc(unc_len + 1, GFP_KERNEL);
        if (full_path == NULL)
                return ERR_PTR(-ENOMEM);
 
        strncpy(full_path, volume_info->UNC, unc_len);
-       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) {
-               int i;
-               for (i = 0; i < unc_len; i++) {
-                       if (full_path[i] == '\\')
-                               full_path[i] = '/';
-               }
-       }
-
-       if (cifs_sb->prepathlen)
-               strncpy(full_path + unc_len, cifs_sb->prepath,
-                               cifs_sb->prepathlen);
-
-       full_path[unc_len + cifs_sb->prepathlen] = 0; /* add trailing null */
+       full_path[unc_len] = 0; /* add trailing null */
+       convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb));
        return full_path;
 }
-#endif
 
-int
-cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
-               char *mount_data_global, const char *devname)
+/*
+ * Perform a dfs referral query for a share and (optionally) prefix
+ *
+ * If a referral is found, cifs_sb->mountdata will be (re-)allocated
+ * to a string containing updated options for the submount.  Otherwise it
+ * will be left untouched.
+ *
+ * Returns the rc from get_dfs_path to the caller, which can be used to
+ * determine whether there were referrals.
+ */
+static int
+expand_dfs_referral(int xid, struct cifs_ses *pSesInfo,
+                   struct smb_vol *volume_info, struct cifs_sb_info *cifs_sb,
+                   int check_prefix)
 {
        int rc;
-       int xid;
-       struct smb_vol *volume_info;
-       struct cifsSesInfo *pSesInfo;
-       struct cifsTconInfo *tcon;
-       struct TCP_Server_Info *srvTcp;
-       char   *full_path;
-       char *mount_data = mount_data_global;
-       struct tcon_link *tlink;
-#ifdef CONFIG_CIFS_DFS_UPCALL
-       struct dfs_info3_param *referrals = NULL;
        unsigned int num_referrals = 0;
-       int referral_walks_count = 0;
-try_mount_again:
+       struct dfs_info3_param *referrals = NULL;
+       char *full_path = NULL, *ref_path = NULL, *mdata = NULL;
+
+       full_path = build_unc_path_to_root(volume_info, cifs_sb);
+       if (IS_ERR(full_path))
+               return PTR_ERR(full_path);
+
+       /* For DFS paths, skip the first '\' of the UNC */
+       ref_path = check_prefix ? full_path + 1 : volume_info->UNC + 1;
+
+       rc = get_dfs_path(xid, pSesInfo , ref_path, cifs_sb->local_nls,
+                         &num_referrals, &referrals,
+                         cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
+
+       if (!rc && num_referrals > 0) {
+               char *fake_devname = NULL;
+
+               mdata = cifs_compose_mount_options(cifs_sb->mountdata,
+                                                  full_path + 1, referrals,
+                                                  &fake_devname);
+
+               free_dfs_info_array(referrals, num_referrals);
+               kfree(fake_devname);
+
+               if (cifs_sb->mountdata != NULL)
+                       kfree(cifs_sb->mountdata);
+
+               if (IS_ERR(mdata)) {
+                       rc = PTR_ERR(mdata);
+                       mdata = NULL;
+               }
+               cifs_sb->mountdata = mdata;
+       }
+       kfree(full_path);
+       return rc;
+}
 #endif
-       rc = 0;
-       tcon = NULL;
-       pSesInfo = NULL;
-       srvTcp = NULL;
-       full_path = NULL;
-       tlink = NULL;
 
-       xid = GetXid();
+int cifs_setup_volume_info(struct smb_vol **pvolume_info, char *mount_data,
+                          const char *devname)
+{
+       struct smb_vol *volume_info;
+       int rc = 0;
+
+       *pvolume_info = NULL;
 
        volume_info = kzalloc(sizeof(struct smb_vol), GFP_KERNEL);
        if (!volume_info) {
@@ -2765,7 +2933,8 @@ try_mount_again:
                goto out;
        }
 
-       if (cifs_parse_mount_options(mount_data, devname, volume_info)) {
+       if (cifs_parse_mount_options(mount_data, devname,
+                                    volume_info)) {
                rc = -EINVAL;
                goto out;
        }
@@ -2797,12 +2966,58 @@ try_mount_again:
                        goto out;
                }
        }
-       cifs_sb->local_nls = volume_info->local_nls;
+
+       *pvolume_info = volume_info;
+       return rc;
+out:
+       cifs_cleanup_volume_info(&volume_info);
+       return rc;
+}
+
+int
+cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
+{
+       int rc = 0;
+       int xid;
+       struct cifs_ses *pSesInfo;
+       struct cifs_tcon *tcon;
+       struct TCP_Server_Info *srvTcp;
+       char   *full_path;
+       struct tcon_link *tlink;
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       int referral_walks_count = 0;
+
+       rc = bdi_setup_and_register(&cifs_sb->bdi, "cifs", BDI_CAP_MAP_COPY);
+       if (rc)
+               return rc;
+
+       cifs_sb->bdi.ra_pages = default_backing_dev_info.ra_pages;
+
+try_mount_again:
+       /* cleanup activities if we're chasing a referral */
+       if (referral_walks_count) {
+               if (tcon)
+                       cifs_put_tcon(tcon);
+               else if (pSesInfo)
+                       cifs_put_smb_ses(pSesInfo);
+
+               cifs_cleanup_volume_info(&volume_info);
+               FreeXid(xid);
+       }
+#endif
+       tcon = NULL;
+       pSesInfo = NULL;
+       srvTcp = NULL;
+       full_path = NULL;
+       tlink = NULL;
+
+       xid = GetXid();
 
        /* get a reference to a tcp session */
        srvTcp = cifs_get_tcp_session(volume_info);
        if (IS_ERR(srvTcp)) {
                rc = PTR_ERR(srvTcp);
+               bdi_destroy(&cifs_sb->bdi);
                goto out;
        }
 
@@ -2814,15 +3029,6 @@ try_mount_again:
                goto mount_fail_check;
        }
 
-       setup_cifs_sb(volume_info, cifs_sb);
-       if (pSesInfo->capabilities & CAP_LARGE_FILES)
-               sb->s_maxbytes = MAX_LFS_FILESIZE;
-       else
-               sb->s_maxbytes = MAX_NON_LFS;
-
-       /* BB FIXME fix time_gran to be larger for LANMAN sessions */
-       sb->s_time_gran = 100;
-
        /* search for existing tcon to this server share */
        tcon = cifs_get_tcon(pSesInfo, volume_info);
        if (IS_ERR(tcon)) {
@@ -2831,40 +3037,59 @@ try_mount_again:
                goto remote_path_check;
        }
 
-       /* do not care if following two calls succeed - informational */
-       if (!tcon->ipc) {
-               CIFSSMBQFSDeviceInfo(xid, tcon);
-               CIFSSMBQFSAttributeInfo(xid, tcon);
-       }
-
        /* tell server which Unix caps we support */
-       if (tcon->ses->capabilities & CAP_UNIX)
+       if (tcon->ses->capabilities & CAP_UNIX) {
                /* reset of caps checks mount to see if unix extensions
                   disabled for just this mount */
-               reset_cifs_unix_caps(xid, tcon, sb, volume_info);
-       else
+               reset_cifs_unix_caps(xid, tcon, cifs_sb, volume_info);
+               if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) &&
+                   (le64_to_cpu(tcon->fsUnixInfo.Capability) &
+                    CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)) {
+                       rc = -EACCES;
+                       goto mount_fail_check;
+               }
+       } else
                tcon->unix_ext = 0; /* server does not support them */
 
-       /* convert forward to back slashes in prepath here if needed */
-       if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0)
-               convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb));
+       /* do not care if following two calls succeed - informational */
+       if (!tcon->ipc) {
+               CIFSSMBQFSDeviceInfo(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_WRITE_X))
-               cifs_sb->wsize = min(cifs_sb->wsize,
-                              (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE));
        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->wsize = cifs_negotiate_wsize(tcon, volume_info);
+
 remote_path_check:
-       /* check if a whole path (including prepath) is not remote */
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       /*
+        * Perform an unconditional check for whether there are DFS
+        * referrals for this path without prefix, to provide support
+        * for DFS referrals from w2k8 servers which don't seem to respond
+        * with PATH_NOT_COVERED to requests that include the prefix.
+        * Chase the referral if found, otherwise continue normally.
+        */
+       if (referral_walks_count == 0) {
+               int refrc = expand_dfs_referral(xid, pSesInfo, volume_info,
+                                               cifs_sb, false);
+               if (!refrc) {
+                       referral_walks_count++;
+                       goto try_mount_again;
+               }
+       }
+#endif
+
+       /* check if a whole path is not remote */
        if (!rc && tcon) {
                /* build_path_to_root works only when we have a valid tcon */
-               full_path = cifs_build_path_to_root(cifs_sb, tcon);
+               full_path = cifs_build_path_to_root(volume_info, cifs_sb, tcon);
                if (full_path == NULL) {
                        rc = -ENOMEM;
                        goto mount_fail_check;
@@ -2890,50 +3115,15 @@ remote_path_check:
                        rc = -ELOOP;
                        goto mount_fail_check;
                }
-               /* convert forward to back slashes in prepath here if needed */
-               if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0)
-                       convert_delimiter(cifs_sb->prepath,
-                                       CIFS_DIR_SEP(cifs_sb));
-               full_path = build_unc_path_to_root(volume_info, cifs_sb);
-               if (IS_ERR(full_path)) {
-                       rc = PTR_ERR(full_path);
-                       goto mount_fail_check;
-               }
 
-               cFYI(1, "Getting referral for: %s", full_path);
-               rc = get_dfs_path(xid, pSesInfo , full_path + 1,
-                       cifs_sb->local_nls, &num_referrals, &referrals,
-                       cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
-               if (!rc && num_referrals > 0) {
-                       char *fake_devname = NULL;
-
-                       if (mount_data != mount_data_global)
-                               kfree(mount_data);
-
-                       mount_data = cifs_compose_mount_options(
-                                       cifs_sb->mountdata, full_path + 1,
-                                       referrals, &fake_devname);
-
-                       free_dfs_info_array(referrals, num_referrals);
-                       kfree(fake_devname);
-                       kfree(full_path);
+               rc = expand_dfs_referral(xid, pSesInfo, volume_info, cifs_sb,
+                                        true);
 
-                       if (IS_ERR(mount_data)) {
-                               rc = PTR_ERR(mount_data);
-                               mount_data = NULL;
-                               goto mount_fail_check;
-                       }
-
-                       if (tcon)
-                               cifs_put_tcon(tcon);
-                       else if (pSesInfo)
-                               cifs_put_smb_ses(pSesInfo);
-
-                       cleanup_volume_info(&volume_info);
+               if (!rc) {
                        referral_walks_count++;
-                       FreeXid(xid);
                        goto try_mount_again;
                }
+               goto mount_fail_check;
 #else /* No DFS support, return error on mount */
                rc = -EOPNOTSUPP;
 #endif
@@ -2966,8 +3156,6 @@ remote_path_check:
 mount_fail_check:
        /* on error free sesinfo and tcon struct if needed */
        if (rc) {
-               if (mount_data != mount_data_global)
-                       kfree(mount_data);
                /* If find_unc succeeded then rc == 0 so we can not end */
                /* up accidentally freeing someone elses tcon struct */
                if (tcon)
@@ -2976,6 +3164,7 @@ mount_fail_check:
                        cifs_put_smb_ses(pSesInfo);
                else
                        cifs_put_tcp_session(srvTcp);
+               bdi_destroy(&cifs_sb->bdi);
                goto out;
        }
 
@@ -2985,14 +3174,17 @@ mount_fail_check:
        password will be freed at unmount time) */
 out:
        /* zero out password before freeing */
-       cleanup_volume_info(&volume_info);
        FreeXid(xid);
        return rc;
 }
 
+/*
+ * Issue a TREE_CONNECT request. Note that for IPC$ shares, that the tcon
+ * pointer may be NULL.
+ */
 int
-CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
-        const char *tree, struct cifsTconInfo *tcon,
+CIFSTCon(unsigned int xid, struct cifs_ses *ses,
+        const char *tree, struct cifs_tcon *tcon,
         const struct nls_table *nls_codepage)
 {
        struct smb_hdr *smb_buffer;
@@ -3024,7 +3216,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
        pSMB->AndXCommand = 0xFF;
        pSMB->Flags = cpu_to_le16(TCON_EXTENDED_SECINFO);
        bcc_ptr = &pSMB->Password[0];
-       if ((ses->server->secMode) & SECMODE_USER) {
+       if (!tcon || (ses->server->sec_mode & SECMODE_USER)) {
                pSMB->PasswordLength = cpu_to_le16(1);  /* minimum */
                *bcc_ptr = 0; /* password is null byte */
                bcc_ptr++;              /* skip password */
@@ -3041,7 +3233,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
                if ((global_secflags & CIFSSEC_MAY_LANMAN) &&
                    (ses->server->secType == LANMAN))
                        calc_lanman_hash(tcon->password, ses->server->cryptkey,
-                                        ses->server->secMode &
+                                        ses->server->sec_mode &
                                            SECMODE_PW_ENCRYPT ? true : false,
                                         bcc_ptr);
                else
@@ -3057,7 +3249,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
                }
        }
 
-       if (ses->server->secMode &
+       if (ses->server->sec_mode &
                        (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED))
                smb_buffer->Flags2 |= SMBFLG2_SECURITY_SIGNATURE;
 
@@ -3083,7 +3275,8 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
        bcc_ptr += strlen("?????");
        bcc_ptr += 1;
        count = bcc_ptr - &pSMB->Password[0];
-       pSMB->hdr.smb_buf_length += count;
+       pSMB->hdr.smb_buf_length = cpu_to_be32(be32_to_cpu(
+                                       pSMB->hdr.smb_buf_length) + count);
        pSMB->ByteCount = cpu_to_le16(count);
 
        rc = SendReceive(xid, ses, smb_buffer, smb_buffer_response, &length,
@@ -3146,13 +3339,12 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
        return rc;
 }
 
-int
-cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)
+void
+cifs_umount(struct cifs_sb_info *cifs_sb)
 {
        struct rb_root *root = &cifs_sb->tlink_tree;
        struct rb_node *node;
        struct tcon_link *tlink;
-       char *tmp;
 
        cancel_delayed_work_sync(&cifs_sb->prune_tlinks);
 
@@ -3169,15 +3361,10 @@ cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)
        }
        spin_unlock(&cifs_sb->tlink_tree_lock);
 
-       tmp = cifs_sb->prepath;
-       cifs_sb->prepathlen = 0;
-       cifs_sb->prepath = NULL;
-       kfree(tmp);
-
-       return 0;
+       bdi_destroy(&cifs_sb->bdi);
 }
 
-int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses)
+int cifs_negotiate_protocol(unsigned int xid, struct cifs_ses *ses)
 {
        int rc = 0;
        struct TCP_Server_Info *server = ses->server;
@@ -3195,7 +3382,7 @@ int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses)
        }
        if (rc == 0) {
                spin_lock(&GlobalMid_Lock);
-               if (server->tcpStatus != CifsExiting)
+               if (server->tcpStatus == CifsNeedNegotiate)
                        server->tcpStatus = CifsGood;
                else
                        rc = -EHOSTDOWN;
@@ -3207,7 +3394,7 @@ int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses)
 }
 
 
-int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses,
+int cifs_setup_session(unsigned int xid, struct cifs_ses *ses,
                        struct nls_table *nls_info)
 {
        int rc = 0;
@@ -3219,7 +3406,7 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses,
                ses->capabilities &= (~CAP_UNIX);
 
        cFYI(1, "Security Mode: 0x%x Capabilities: 0x%x TimeAdjust: %d",
-                server->secMode, server->capabilities, server->timeAdj);
+                server->sec_mode, server->capabilities, server->timeAdj);
 
        rc = CIFS_SessSetup(xid, ses, nls_info);
        if (rc) {
@@ -3251,14 +3438,16 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses,
        return rc;
 }
 
-static struct cifsTconInfo *
+static struct cifs_tcon *
 cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
 {
-       struct cifsTconInfo *master_tcon = cifs_sb_master_tcon(cifs_sb);
-       struct cifsSesInfo *ses;
-       struct cifsTconInfo *tcon = NULL;
+       struct cifs_tcon *master_tcon = cifs_sb_master_tcon(cifs_sb);
+       struct cifs_ses *ses;
+       struct cifs_tcon *tcon = NULL;
        struct smb_vol *vol_info;
-       char username[MAX_USERNAME_SIZE + 1];
+       char username[28]; /* big enough for "krb50x" + hex of ULONG_MAX 6+16 */
+                          /* We used to have this as MAX_USERNAME which is   */
+                          /* way too big now (256 instead of 32) */
 
        vol_info = kzalloc(sizeof(*vol_info), GFP_KERNEL);
        if (vol_info == NULL) {
@@ -3287,7 +3476,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
 
        ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);
        if (IS_ERR(ses)) {
-               tcon = (struct cifsTconInfo *)ses;
+               tcon = (struct cifs_tcon *)ses;
                cifs_put_tcp_session(master_tcon->ses->server);
                goto out;
        }
@@ -3306,13 +3495,7 @@ out:
        return tcon;
 }
 
-static inline struct tcon_link *
-cifs_sb_master_tlink(struct cifs_sb_info *cifs_sb)
-{
-       return cifs_sb->master_tlink;
-}
-
-struct cifsTconInfo *
+struct cifs_tcon *
 cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb)
 {
        return tlink_tcon(cifs_sb_master_tlink(cifs_sb));