Merge branch 'stable/bug.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git...
[pandora-kernel.git] / fs / cifs / connect.c
index 12cf72d..ccc1afa 100644 (file)
@@ -65,6 +65,8 @@ 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);
 static void cifs_prune_tlinks(struct work_struct *work);
+static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data,
+                                       const char *devname);
 
 /*
  * cifs tcp session reconnection
@@ -2240,8 +2242,8 @@ cifs_match_super(struct super_block *sb, void *data)
 
        rc = compare_mount_options(sb, mnt_data);
 out:
-       cifs_put_tlink(tlink);
        spin_unlock(&cifs_tcp_ses_lock);
+       cifs_put_tlink(tlink);
        return rc;
 }
 
@@ -2474,14 +2476,6 @@ generic_ip_connect(struct TCP_Server_Info *server)
        if (rc < 0)
                return rc;
 
-       rc = socket->ops->connect(socket, saddr, slen, 0);
-       if (rc < 0) {
-               cFYI(1, "Error %d connecting to server", rc);
-               sock_release(socket);
-               server->ssocket = NULL;
-               return rc;
-       }
-
        /*
         * Eventually check for other socket options to change from
         * the default. sock_setsockopt not used because it expects
@@ -2510,6 +2504,14 @@ generic_ip_connect(struct TCP_Server_Info *server)
                 socket->sk->sk_sndbuf,
                 socket->sk->sk_rcvbuf, socket->sk->sk_rcvtimeo);
 
+       rc = socket->ops->connect(socket, saddr, slen, 0);
+       if (rc < 0) {
+               cFYI(1, "Error %d connecting to server", rc);
+               sock_release(socket);
+               server->ssocket = NULL;
+               return rc;
+       }
+
        if (sport == htons(RFC1001_PORT))
                rc = ip_rfc1001_connect(server);
 
@@ -2546,7 +2548,7 @@ ip_connect(struct TCP_Server_Info *server)
 }
 
 void reset_cifs_unix_caps(int xid, struct cifs_tcon *tcon,
-                         struct super_block *sb, struct smb_vol *vol_info)
+                         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
@@ -2600,22 +2602,23 @@ void reset_cifs_unix_caps(int xid, struct cifs_tcon *tcon,
                        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;
                }
 
-               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");
                        }
                }
@@ -2662,6 +2665,9 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info,
 {
        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);
@@ -2750,21 +2756,21 @@ 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 - PAGE_CACHE_SIZE.
+ * allow up to 2^24-1, minus the size of a 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.
+ * 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)
+#define CIFS_MAX_WSIZE ((1<<24) - 1 - sizeof(WRITE_REQ) + 4)
 
 /*
- * 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.
+ * 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
+ * to the maximum size described by RFC1002.
  */
-#define CIFS_MAX_RFC1001_WSIZE (128 * 1024 - PAGE_CACHE_SIZE)
+#define CIFS_MAX_RFC1002_WSIZE (128 * 1024 - sizeof(WRITE_REQ) + 4)
 
 /*
  * The default wsize is 1M. find_get_pages seems to return a maximum of 256
@@ -2783,11 +2789,18 @@ cifs_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info)
 
        /* 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);
+               wsize = min_t(unsigned int, wsize, CIFS_MAX_RFC1002_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);
+       /*
+        * no CAP_LARGE_WRITE_X or is signing enabled without CAP_UNIX set?
+        * Limit it to max buffer offered by the server, minus the size of the
+        * WRITEX header, not including the 4 byte RFC1001 length.
+        */
+       if (!(server->capabilities & CAP_LARGE_WRITE_X) ||
+           (!(server->capabilities & CAP_UNIX) &&
+            (server->sec_mode & (SECMODE_SIGN_ENABLED|SECMODE_SIGN_REQUIRED))))
+               wsize = min_t(unsigned int, wsize,
+                               server->maxBuf - sizeof(WRITE_REQ) + 4);
 
        /* hard limit of CIFS_MAX_WSIZE */
        wsize = min_t(unsigned int, wsize, CIFS_MAX_WSIZE);
@@ -2819,15 +2832,9 @@ is_path_accessible(int xid, struct cifs_tcon *tcon,
        return rc;
 }
 
-void
-cifs_cleanup_volume_info(struct smb_vol **pvolume_info)
+static void
+cleanup_volume_info_contents(struct smb_vol *volume_info)
 {
-       struct smb_vol *volume_info;
-
-       if (!pvolume_info || !*pvolume_info)
-               return;
-
-       volume_info = *pvolume_info;
        kfree(volume_info->username);
        kzfree(volume_info->password);
        kfree(volume_info->UNC);
@@ -2835,28 +2842,44 @@ cifs_cleanup_volume_info(struct smb_vol **pvolume_info)
        kfree(volume_info->domainname);
        kfree(volume_info->iocharset);
        kfree(volume_info->prepath);
+}
+
+void
+cifs_cleanup_volume_info(struct smb_vol *volume_info)
+{
+       if (!volume_info)
+               return;
+       cleanup_volume_info_contents(volume_info);
        kfree(volume_info);
-       *pvolume_info = NULL;
-       return;
 }
 
+
 #ifdef CONFIG_CIFS_DFS_UPCALL
 /* build_path_to_root returns full path to root when
  * we do not have an exiting connection (tcon) */
 static char *
-build_unc_path_to_root(const struct smb_vol *volume_info,
+build_unc_path_to_root(const struct smb_vol *vol,
                const struct cifs_sb_info *cifs_sb)
 {
-       char *full_path;
+       char *full_path, *pos;
+       unsigned int pplen = vol->prepath ? strlen(vol->prepath) : 0;
+       unsigned int unc_len = strnlen(vol->UNC, MAX_TREE_SIZE + 1);
 
-       int unc_len = strnlen(volume_info->UNC, MAX_TREE_SIZE + 1);
-       full_path = kmalloc(unc_len + 1, GFP_KERNEL);
+       full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);
        if (full_path == NULL)
                return ERR_PTR(-ENOMEM);
 
-       strncpy(full_path, volume_info->UNC, unc_len);
-       full_path[unc_len] = 0; /* add trailing null */
+       strncpy(full_path, vol->UNC, unc_len);
+       pos = full_path + unc_len;
+
+       if (pplen) {
+               strncpy(pos, vol->prepath, pplen);
+               pos += pplen;
+       }
+
+       *pos = '\0'; /* add trailing null */
        convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb));
+       cFYI(1, "%s: full_path=%s", __func__, full_path);
        return full_path;
 }
 
@@ -2899,15 +2922,18 @@ expand_dfs_referral(int xid, struct cifs_ses *pSesInfo,
                                                   &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;
+               } else {
+                       cleanup_volume_info_contents(volume_info);
+                       memset(volume_info, '\0', sizeof(*volume_info));
+                       rc = cifs_setup_volume_info(volume_info, mdata,
+                                                       fake_devname);
                }
+               kfree(fake_devname);
+               kfree(cifs_sb->mountdata);
                cifs_sb->mountdata = mdata;
        }
        kfree(full_path);
@@ -2915,29 +2941,20 @@ expand_dfs_referral(int xid, struct cifs_ses *pSesInfo,
 }
 #endif
 
-int cifs_setup_volume_info(struct smb_vol **pvolume_info, char *mount_data,
-                          const char *devname)
+static int
+cifs_setup_volume_info(struct smb_vol *volume_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) {
-               rc = -ENOMEM;
-               goto out;
-       }
-
-       if (cifs_parse_mount_options(mount_data, devname,
-                                    volume_info)) {
-               rc = -EINVAL;
-               goto out;
-       }
+       if (cifs_parse_mount_options(mount_data, devname, volume_info))
+               return -EINVAL;
 
        if (volume_info->nullauth) {
                cFYI(1, "null user");
-               volume_info->username = "";
+               volume_info->username = kzalloc(1, GFP_KERNEL);
+               if (volume_info->username == NULL)
+                       return -ENOMEM;
        } else if (volume_info->username) {
                /* BB fixme parse for domain name here */
                cFYI(1, "Username: %s", volume_info->username);
@@ -2945,8 +2962,7 @@ int cifs_setup_volume_info(struct smb_vol **pvolume_info, char *mount_data,
                cifserror("No username specified");
        /* In userspace mount helper we can get user name from alternate
           locations such as env variables and files on disk */
-               rc = -EINVAL;
-               goto out;
+               return -EINVAL;
        }
 
        /* this is needed for ASCII cp to Unicode converts */
@@ -2958,21 +2974,34 @@ int cifs_setup_volume_info(struct smb_vol **pvolume_info, char *mount_data,
                if (volume_info->local_nls == NULL) {
                        cERROR(1, "CIFS mount error: iocharset %s not found",
                                 volume_info->iocharset);
-                       rc = -ELIBACC;
-                       goto out;
+                       return -ELIBACC;
                }
        }
 
-       *pvolume_info = volume_info;
-       return rc;
-out:
-       cifs_cleanup_volume_info(&volume_info);
        return rc;
 }
 
+struct smb_vol *
+cifs_get_volume_info(char *mount_data, const char *devname)
+{
+       int rc;
+       struct smb_vol *volume_info;
+
+       volume_info = kzalloc(sizeof(struct smb_vol), GFP_KERNEL);
+       if (!volume_info)
+               return ERR_PTR(-ENOMEM);
+
+       rc = cifs_setup_volume_info(volume_info, mount_data, devname);
+       if (rc) {
+               cifs_cleanup_volume_info(volume_info);
+               volume_info = ERR_PTR(rc);
+       }
+
+       return volume_info;
+}
+
 int
-cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
-          struct smb_vol *volume_info, const char *devname)
+cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
 {
        int rc = 0;
        int xid;
@@ -2983,6 +3012,15 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
        struct tcon_link *tlink;
 #ifdef CONFIG_CIFS_DFS_UPCALL
        int referral_walks_count = 0;
+#endif
+
+       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;
+
+#ifdef CONFIG_CIFS_DFS_UPCALL
 try_mount_again:
        /* cleanup activities if we're chasing a referral */
        if (referral_walks_count) {
@@ -2991,7 +3029,6 @@ try_mount_again:
                else if (pSesInfo)
                        cifs_put_smb_ses(pSesInfo);
 
-               cifs_cleanup_volume_info(&volume_info);
                FreeXid(xid);
        }
 #endif
@@ -3007,6 +3044,7 @@ try_mount_again:
        srvTcp = cifs_get_tcp_session(volume_info);
        if (IS_ERR(srvTcp)) {
                rc = PTR_ERR(srvTcp);
+               bdi_destroy(&cifs_sb->bdi);
                goto out;
        }
 
@@ -3018,14 +3056,6 @@ try_mount_again:
                goto mount_fail_check;
        }
 
-       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)) {
@@ -3038,7 +3068,7 @@ try_mount_again:
        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);
+               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)) {
@@ -3161,6 +3191,7 @@ mount_fail_check:
                        cifs_put_smb_ses(pSesInfo);
                else
                        cifs_put_tcp_session(srvTcp);
+               bdi_destroy(&cifs_sb->bdi);
                goto out;
        }
 
@@ -3335,8 +3366,8 @@ CIFSTCon(unsigned int xid, struct cifs_ses *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;
@@ -3357,7 +3388,10 @@ cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)
        }
        spin_unlock(&cifs_sb->tlink_tree_lock);
 
-       return 0;
+       bdi_destroy(&cifs_sb->bdi);
+       kfree(cifs_sb->mountdata);
+       unload_nls(cifs_sb->local_nls);
+       kfree(cifs_sb);
 }
 
 int cifs_negotiate_protocol(unsigned int xid, struct cifs_ses *ses)
@@ -3451,7 +3485,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
                goto out;
        }
 
-       snprintf(username, MAX_USERNAME_SIZE, "krb50x%x", fsuid);
+       snprintf(username, sizeof(username), "krb50x%x", fsuid);
        vol_info->username = username;
        vol_info->local_nls = cifs_sb->local_nls;
        vol_info->linux_uid = fsuid;