[v1,01/14] cifs: Refactor out cifs_mount()

Message ID 20181115142103.24617-2-aaptel@suse.com
State New
Headers show
Series
  • DFS failover
Related show

Commit Message

Aurélien Aptel Nov. 15, 2018, 2:20 p.m.
From: Paulo Alcantara <paulo@paulo.ac>

* Split and refactor the very large function cifs_mount() in multiple
  functions:

- tcp, ses and tcon setup to mount_get_conns()
- tcp, ses and tcon cleanup in mount_put_conns()
- tcon tlink setup to mount_setup_tlink()
- remote path checking to is_path_remote()

* Implement 2 version of cifs_mount() for DFS-enabled builds and
  non-DFS-enabled builds (CONFIG_CIFS_DFS_UPCALL).

In preparation for DFS failover support.

Signed-off-by: Paulo Alcantara <palcantara@suse.de>
Signed-off-by: Aurelien Aptel <aaptel@suse.com>
---
 fs/cifs/cifsproto.h |   4 +-
 fs/cifs/connect.c   | 441 ++++++++++++++++++++++++++++++----------------------
 2 files changed, 262 insertions(+), 183 deletions(-)

Comments

Steve French Dec. 24, 2018, 5:03 a.m. | #1
After I rereviewed this - I updated with minor changes and remerged to
cifs-2.6.git for-next.

$ diff -a  saved-0015-cifs-Refactor-out-cifs_mount.patch
0015-cifs-Refactor-out-cifs_mount.patch
180c180
< +    tlink = kzalloc(sizeof *tlink, GFP_KERNEL);
---
> +    tlink = kzalloc(sizeof(*tlink), GFP_KERNEL);
259c259
< +        return -ENOSYS;
---
> +        return -EOPNOTSUPP;
365c365
< +    if (rc == -EACCES || rc == -EOPNOTSUPP)
---
> +    if ((rc == -EACCES) || (rc == -EOPNOTSUPP) || (ses == NULL) || (server == NULL))


Updated patch attached

On Thu, Nov 15, 2018 at 8:21 AM Aurelien Aptel <aaptel@suse.com> wrote:
>
> From: Paulo Alcantara <paulo@paulo.ac>
>
> * Split and refactor the very large function cifs_mount() in multiple
>   functions:
>
> - tcp, ses and tcon setup to mount_get_conns()
> - tcp, ses and tcon cleanup in mount_put_conns()
> - tcon tlink setup to mount_setup_tlink()
> - remote path checking to is_path_remote()
>
> * Implement 2 version of cifs_mount() for DFS-enabled builds and
>   non-DFS-enabled builds (CONFIG_CIFS_DFS_UPCALL).
>
> In preparation for DFS failover support.
>
> Signed-off-by: Paulo Alcantara <palcantara@suse.de>
> Signed-off-by: Aurelien Aptel <aaptel@suse.com>
> ---
>  fs/cifs/cifsproto.h |   4 +-
>  fs/cifs/connect.c   | 441 ++++++++++++++++++++++++++++++----------------------
>  2 files changed, 262 insertions(+), 183 deletions(-)
>
> diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
> index fa361bc00602..f4dd2a3795dd 100644
> --- a/fs/cifs/cifsproto.h
> +++ b/fs/cifs/cifsproto.h
> @@ -213,7 +213,7 @@ extern int cifs_match_super(struct super_block *, void *);
>  extern void cifs_cleanup_volume_info(struct smb_vol *pvolume_info);
>  extern struct smb_vol *cifs_get_volume_info(char *mount_data,
>                                             const char *devname, bool is_smb3);
> -extern int cifs_mount(struct cifs_sb_info *, struct smb_vol *);
> +extern int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol);
>  extern void cifs_umount(struct cifs_sb_info *);
>  extern void cifs_mark_open_files_invalid(struct cifs_tcon *tcon);
>  extern void cifs_reopen_persistent_handles(struct cifs_tcon *tcon);
> @@ -524,6 +524,8 @@ extern int E_md4hash(const unsigned char *passwd, unsigned char *p16,
>                         const struct nls_table *codepage);
>  extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8,
>                         unsigned char *p24);
> +extern void
> +cifs_cleanup_volume_info_contents(struct smb_vol *volume_info);
>
>  void cifs_readdata_release(struct kref *refcount);
>  int cifs_async_readv(struct cifs_readdata *rdata);
> diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
> index 6f24f129a751..3caa2021a5d6 100644
> --- a/fs/cifs/connect.c
> +++ b/fs/cifs/connect.c
> @@ -3746,8 +3746,8 @@ int cifs_setup_cifs_sb(struct smb_vol *pvolume_info,
>         return 0;
>  }
>
> -static void
> -cleanup_volume_info_contents(struct smb_vol *volume_info)
> +void
> +cifs_cleanup_volume_info_contents(struct smb_vol *volume_info)
>  {
>         kfree(volume_info->username);
>         kzfree(volume_info->password);
> @@ -3762,10 +3762,136 @@ cifs_cleanup_volume_info(struct smb_vol *volume_info)
>  {
>         if (!volume_info)
>                 return;
> -       cleanup_volume_info_contents(volume_info);
> +       cifs_cleanup_volume_info_contents(volume_info);
>         kfree(volume_info);
>  }
>
> +/* Release all succeed connections */
> +static inline void mount_put_conns(struct cifs_sb_info *cifs_sb,
> +                                  unsigned int xid,
> +                                  struct TCP_Server_Info *server,
> +                                  struct cifs_ses *ses, struct cifs_tcon *tcon)
> +{
> +       int rc = 0;
> +
> +       if (tcon)
> +               cifs_put_tcon(tcon);
> +       else if (ses)
> +               cifs_put_smb_ses(ses);
> +       else if (server)
> +               cifs_put_tcp_session(server, 0);
> +       cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS;
> +       free_xid(xid);
> +}
> +
> +/* Get connections for tcp, ses and tcon */
> +static int mount_get_conns(struct smb_vol *vol, struct cifs_sb_info *cifs_sb,
> +                          unsigned int *xid,
> +                          struct TCP_Server_Info **nserver,
> +                          struct cifs_ses **nses, struct cifs_tcon **ntcon)
> +{
> +       int rc = 0;
> +       struct TCP_Server_Info *server;
> +       struct cifs_ses *ses;
> +       struct cifs_tcon *tcon;
> +
> +       *nserver = NULL;
> +       *nses = NULL;
> +       *ntcon = NULL;
> +
> +       *xid = get_xid();
> +
> +       /* get a reference to a tcp session */
> +       server = cifs_get_tcp_session(vol);
> +       if (IS_ERR(server)) {
> +               rc = PTR_ERR(server);
> +               return rc;
> +       }
> +
> +       *nserver = server;
> +
> +       if ((vol->max_credits < 20) || (vol->max_credits > 60000))
> +               server->max_credits = SMB2_MAX_CREDITS_AVAILABLE;
> +       else
> +               server->max_credits = vol->max_credits;
> +
> +       /* get a reference to a SMB session */
> +       ses = cifs_get_smb_ses(server, vol);
> +       if (IS_ERR(ses)) {
> +               rc = PTR_ERR(ses);
> +               return rc;
> +       }
> +
> +       *nses = ses;
> +
> +       if ((vol->persistent == true) && (!(ses->server->capabilities &
> +                                           SMB2_GLOBAL_CAP_PERSISTENT_HANDLES))) {
> +               cifs_dbg(VFS, "persistent handles not supported by server\n");
> +               return -EOPNOTSUPP;
> +       }
> +
> +       /* search for existing tcon to this server share */
> +       tcon = cifs_get_tcon(ses, vol);
> +       if (IS_ERR(tcon)) {
> +               rc = PTR_ERR(tcon);
> +               return rc;
> +       }
> +
> +       *ntcon = tcon;
> +
> +       /* if new SMB3.11 POSIX extensions are supported do not remap / and \ */
> +       if (tcon->posix_extensions)
> +               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_POSIX_PATHS;
> +
> +       /* tell server which Unix caps we support */
> +       if (cap_unix(tcon->ses)) {
> +               /*
> +                * reset of caps checks mount to see if unix extensions disabled
> +                * for just this mount.
> +                */
> +               reset_cifs_unix_caps(*xid, tcon, cifs_sb, vol);
> +               if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) &&
> +                   (le64_to_cpu(tcon->fsUnixInfo.Capability) &
> +                    CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP))
> +                       return -EACCES;
> +       } else
> +               tcon->unix_ext = 0; /* server does not support them */
> +
> +       /* do not care if a following call succeed - informational */
> +       if (!tcon->pipe && server->ops->qfs_tcon)
> +               server->ops->qfs_tcon(*xid, tcon);
> +
> +       cifs_sb->wsize = server->ops->negotiate_wsize(tcon, vol);
> +       cifs_sb->rsize = server->ops->negotiate_rsize(tcon, vol);
> +
> +       return 0;
> +}
> +
> +static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
> +                            struct cifs_tcon *tcon)
> +{
> +       struct tcon_link *tlink;
> +
> +       /* hang the tcon off of the superblock */
> +       tlink = kzalloc(sizeof *tlink, GFP_KERNEL);
> +       if (tlink == NULL)
> +               return -ENOMEM;
> +
> +       tlink->tl_uid = ses->linux_uid;
> +       tlink->tl_tcon = tcon;
> +       tlink->tl_time = jiffies;
> +       set_bit(TCON_LINK_MASTER, &tlink->tl_flags);
> +       set_bit(TCON_LINK_IN_TREE, &tlink->tl_flags);
> +
> +       cifs_sb->master_tlink = tlink;
> +       spin_lock(&cifs_sb->tlink_tree_lock);
> +       tlink_rb_insert(&cifs_sb->tlink_tree, tlink);
> +       spin_unlock(&cifs_sb->tlink_tree_lock);
> +
> +       queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks,
> +                               TLINK_IDLE_EXPIRE);
> +       return 0;
> +}
>
>  #ifdef CONFIG_CIFS_DFS_UPCALL
>  /*
> @@ -3845,7 +3971,7 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
>                         rc = PTR_ERR(mdata);
>                         mdata = NULL;
>                 } else {
> -                       cleanup_volume_info_contents(volume_info);
> +                       cifs_cleanup_volume_info_contents(volume_info);
>                         rc = cifs_setup_volume_info(volume_info, mdata,
>                                                         fake_devname, false);
>                 }
> @@ -3954,107 +4080,77 @@ cifs_are_all_path_components_accessible(struct TCP_Server_Info *server,
>         return rc;
>  }
>
> -int
> -cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
> +/*
> + * Check if path is remote (e.g. a DFS share). Return -EREMOTE if it is,
> + * otherwise 0.
> + */
> +static int is_path_remote(struct cifs_sb_info *cifs_sb, struct smb_vol *vol,
> +                         const unsigned int xid,
> +                         struct TCP_Server_Info *server,
> +                         struct cifs_tcon *tcon)
>  {
>         int rc;
> -       unsigned int xid;
> -       struct cifs_ses *ses;
> -       struct cifs_tcon *tcon;
> -       struct TCP_Server_Info *server;
> -       char   *full_path;
> -       struct tcon_link *tlink;
> -#ifdef CONFIG_CIFS_DFS_UPCALL
> -       int referral_walks_count = 0;
> -#endif
> -
> -#ifdef CONFIG_CIFS_DFS_UPCALL
> -try_mount_again:
> -       /* cleanup activities if we're chasing a referral */
> -       if (referral_walks_count) {
> -               if (tcon)
> -                       cifs_put_tcon(tcon);
> -               else if (ses)
> -                       cifs_put_smb_ses(ses);
> -
> -               cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS;
> +       char *full_path;
>
> -               free_xid(xid);
> -       }
> -#endif
> -       rc = 0;
> -       tcon = NULL;
> -       ses = NULL;
> -       server = NULL;
> -       full_path = NULL;
> -       tlink = NULL;
> +       if (!server->ops->is_path_accessible)
> +               return -ENOSYS;
>
> -       xid = get_xid();
> +       /*
> +        * cifs_build_path_to_root works only when we have a valid tcon
> +        */
> +       full_path = cifs_build_path_to_root(vol, cifs_sb, tcon,
> +                                           tcon->Flags & SMB_SHARE_IS_IN_DFS);
> +       if (full_path == NULL)
> +               return -ENOMEM;
>
> -       /* get a reference to a tcp session */
> -       server = cifs_get_tcp_session(volume_info);
> -       if (IS_ERR(server)) {
> -               rc = PTR_ERR(server);
> -               goto out;
> -       }
> -       if ((volume_info->max_credits < 20) ||
> -            (volume_info->max_credits > 60000))
> -               server->max_credits = SMB2_MAX_CREDITS_AVAILABLE;
> -       else
> -               server->max_credits = volume_info->max_credits;
> -       /* get a reference to a SMB session */
> -       ses = cifs_get_smb_ses(server, volume_info);
> -       if (IS_ERR(ses)) {
> -               rc = PTR_ERR(ses);
> -               ses = NULL;
> -               goto mount_fail_check;
> -       }
> +       cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path);
>
> -       if ((volume_info->persistent == true) && ((ses->server->capabilities &
> -               SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) == 0)) {
> -               cifs_dbg(VFS, "persistent handles not supported by server\n");
> -               rc = -EOPNOTSUPP;
> -               goto mount_fail_check;
> +       rc = server->ops->is_path_accessible(xid, tcon, cifs_sb,
> +                                            full_path);
> +       if (rc != 0 && rc != -EREMOTE) {
> +               kfree(full_path);
> +               return rc;
>         }
>
> -       /* search for existing tcon to this server share */
> -       tcon = cifs_get_tcon(ses, volume_info);
> -       if (IS_ERR(tcon)) {
> -               rc = PTR_ERR(tcon);
> -               tcon = NULL;
> -               if (rc == -EACCES)
> -                       goto mount_fail_check;
> -
> -               goto remote_path_check;
> +       if (rc != -EREMOTE) {
> +               rc = cifs_are_all_path_components_accessible(server, xid, tcon,
> +                                                            cifs_sb,
> +                                                            full_path);
> +               if (rc != 0) {
> +                       cifs_dbg(VFS, "cannot query dirs between root and final path, "
> +                                "enabling CIFS_MOUNT_USE_PREFIX_PATH\n");
> +                       cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
> +                       rc = 0;
> +               }
>         }
>
> -       /* if new SMB3.11 POSIX extensions are supported do not remap / and \ */
> -       if (tcon->posix_extensions)
> -               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_POSIX_PATHS;
> +       kfree(full_path);
> +       return rc;
> +}
>
> -       /* tell server which Unix caps we support */
> -       if (cap_unix(tcon->ses)) {
> -               /* reset of caps checks mount to see if unix extensions
> -                  disabled for just this mount */
> -               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 */
> +#ifdef CONFIG_CIFS_DFS_UPCALL
> +int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
> +{
> +       int rc = 0;
> +       unsigned int xid;
> +       struct cifs_ses *ses;
> +       struct cifs_tcon *tcon = NULL;
> +       struct TCP_Server_Info *server;
> +       char *old_mountdata;
> +       int count;
>
> -       /* do not care if a following call succeed - informational */
> -       if (!tcon->pipe && server->ops->qfs_tcon)
> -               server->ops->qfs_tcon(xid, tcon);
> +       rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
> +       if (!rc && tcon) {
> +               rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
> +               if (!rc)
> +                       goto out;
> +               if (rc != -EREMOTE)
> +                       goto error;
> +       }
> +       if (rc == -EACCES || rc == -EOPNOTSUPP)
> +               goto error;
>
> -       cifs_sb->wsize = server->ops->negotiate_wsize(tcon, volume_info);
> -       cifs_sb->rsize = server->ops->negotiate_rsize(tcon, volume_info);
>
> -remote_path_check:
> -#ifdef CONFIG_CIFS_DFS_UPCALL
>         /*
>          * Perform an unconditional check for whether there are DFS
>          * referrals for this path without prefix, to provide support
> @@ -4062,119 +4158,100 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
>          * 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, ses, volume_info, cifs_sb,
> -                                               false);
> -               if (!refrc) {
> -                       referral_walks_count++;
> -                       goto try_mount_again;
> -               }
> +       old_mountdata = cifs_sb->mountdata;
> +       (void)expand_dfs_referral(xid, ses, vol, cifs_sb, false);
> +
> +       if (cifs_sb->mountdata == NULL) {
> +               rc = -ENOENT;
> +               goto error;
>         }
> -#endif
>
> -       /* check if a whole path is not remote */
> -       if (!rc && tcon) {
> -               if (!server->ops->is_path_accessible) {
> -                       rc = -ENOSYS;
> -                       goto mount_fail_check;
> +       if (cifs_sb->mountdata != old_mountdata) {
> +               /* If we were redirected, reconnect to new target server */
> +               mount_put_conns(cifs_sb, xid, server, ses, tcon);
> +               rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
> +       }
> +       if (rc) {
> +               if (rc == -EACCES || rc == -EOPNOTSUPP)
> +                       goto error;
> +       }
> +
> +       for (count = 1; ;) {
> +               if (!rc && tcon) {
> +                       rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
> +                       if (!rc || rc != -EREMOTE)
> +                               break;
>                 }
>                 /*
> -                * cifs_build_path_to_root works only when we have a valid tcon
> +                * BB: when we implement proper loop detection,
> +                *     we will remove this check. But now we need it
> +                *     to prevent an indefinite loop if 'DFS tree' is
> +                *     misconfigured (i.e. has loops).
>                  */
> -               full_path = cifs_build_path_to_root(volume_info, cifs_sb, tcon,
> -                                       tcon->Flags & SMB_SHARE_IS_IN_DFS);
> -               if (full_path == NULL) {
> -                       rc = -ENOMEM;
> -                       goto mount_fail_check;
> -               }
> -               rc = server->ops->is_path_accessible(xid, tcon, cifs_sb,
> -                                                    full_path);
> -               if (rc != 0 && rc != -EREMOTE) {
> -                       kfree(full_path);
> -                       goto mount_fail_check;
> -               }
> -
> -               if (rc != -EREMOTE) {
> -                       rc = cifs_are_all_path_components_accessible(server,
> -                                                            xid, tcon, cifs_sb,
> -                                                            full_path);
> -                       if (rc != 0) {
> -                               cifs_dbg(VFS, "cannot query dirs between root and final path, "
> -                                        "enabling CIFS_MOUNT_USE_PREFIX_PATH\n");
> -                               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
> -                               rc = 0;
> -                       }
> -               }
> -               kfree(full_path);
> -       }
> -
> -       /* get referral if needed */
> -       if (rc == -EREMOTE) {
> -#ifdef CONFIG_CIFS_DFS_UPCALL
> -               if (referral_walks_count > MAX_NESTED_LINKS) {
> -                       /*
> -                        * BB: when we implement proper loop detection,
> -                        *     we will remove this check. But now we need it
> -                        *     to prevent an indefinite loop if 'DFS tree' is
> -                        *     misconfigured (i.e. has loops).
> -                        */
> +               if (count++ > MAX_NESTED_LINKS) {
>                         rc = -ELOOP;
> -                       goto mount_fail_check;
> +                       break;
>                 }
>
> -               rc = expand_dfs_referral(xid, ses, volume_info, cifs_sb, true);
> +               old_mountdata = cifs_sb->mountdata;
> +               rc = expand_dfs_referral(xid, tcon->ses, vol, cifs_sb,
> +                                        true);
> +               if (rc)
> +                       break;
>
> -               if (!rc) {
> -                       referral_walks_count++;
> -                       goto try_mount_again;
> +               if (cifs_sb->mountdata != old_mountdata) {
> +                       mount_put_conns(cifs_sb, xid, server, ses, tcon);
> +                       rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses,
> +                                            &tcon);
> +               }
> +               if (rc) {
> +                       if (rc == -EACCES || rc == -EOPNOTSUPP || !server ||
> +                           !ses)
> +                               goto error;
>                 }
> -               goto mount_fail_check;
> -#else /* No DFS support, return error on mount */
> -               rc = -EOPNOTSUPP;
> -#endif
>         }
>
>         if (rc)
> -               goto mount_fail_check;
> -
> -       /* now, hang the tcon off of the superblock */
> -       tlink = kzalloc(sizeof *tlink, GFP_KERNEL);
> -       if (tlink == NULL) {
> -               rc = -ENOMEM;
> -               goto mount_fail_check;
> -       }
> +               goto error;
>
> -       tlink->tl_uid = ses->linux_uid;
> -       tlink->tl_tcon = tcon;
> -       tlink->tl_time = jiffies;
> -       set_bit(TCON_LINK_MASTER, &tlink->tl_flags);
> -       set_bit(TCON_LINK_IN_TREE, &tlink->tl_flags);
> +out:
> +       free_xid(xid);
> +       return mount_setup_tlink(cifs_sb, ses, tcon);
>
> -       cifs_sb->master_tlink = tlink;
> -       spin_lock(&cifs_sb->tlink_tree_lock);
> -       tlink_rb_insert(&cifs_sb->tlink_tree, tlink);
> -       spin_unlock(&cifs_sb->tlink_tree_lock);
> +error:
> +       mount_put_conns(cifs_sb, xid, server, ses, tcon);
> +       return rc;
> +}
> +#else
> +int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
> +{
> +       int rc = 0;
> +       unsigned int xid;
> +       struct cifs_ses *ses;
> +       struct cifs_tcon *tcon;
> +       struct TCP_Server_Info *server;
>
> -       queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks,
> -                               TLINK_IDLE_EXPIRE);
> +       rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
> +       if (rc)
> +               goto error;
>
> -mount_fail_check:
> -       /* on error free sesinfo and tcon struct if needed */
> -       if (rc) {
> -               /* If find_unc succeeded then rc == 0 so we can not end */
> -               /* up accidentally freeing someone elses tcon struct */
> -               if (tcon)
> -                       cifs_put_tcon(tcon);
> -               else if (ses)
> -                       cifs_put_smb_ses(ses);
> -               else
> -                       cifs_put_tcp_session(server, 0);
> +       if (tcon) {
> +               rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
> +               if (rc == -EREMOTE)
> +                       rc = -EOPNOTSUPP;
> +               if (rc)
> +                       goto error;
>         }
>
> -out:
>         free_xid(xid);
> +
> +       return mount_setup_tlink(cifs_sb, ses, tcon);
> +
> +error:
> +       mount_put_conns(cifs_sb, xid, server, ses, tcon);
>         return rc;
>  }
> +#endif
>
>  /*
>   * Issue a TREE_CONNECT request.
> --
> 2.13.7
>

Patch

diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index fa361bc00602..f4dd2a3795dd 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -213,7 +213,7 @@  extern int cifs_match_super(struct super_block *, void *);
 extern void cifs_cleanup_volume_info(struct smb_vol *pvolume_info);
 extern struct smb_vol *cifs_get_volume_info(char *mount_data,
 					    const char *devname, bool is_smb3);
-extern int cifs_mount(struct cifs_sb_info *, struct smb_vol *);
+extern int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol);
 extern void cifs_umount(struct cifs_sb_info *);
 extern void cifs_mark_open_files_invalid(struct cifs_tcon *tcon);
 extern void cifs_reopen_persistent_handles(struct cifs_tcon *tcon);
@@ -524,6 +524,8 @@  extern int E_md4hash(const unsigned char *passwd, unsigned char *p16,
 			const struct nls_table *codepage);
 extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8,
 			unsigned char *p24);
+extern void
+cifs_cleanup_volume_info_contents(struct smb_vol *volume_info);
 
 void cifs_readdata_release(struct kref *refcount);
 int cifs_async_readv(struct cifs_readdata *rdata);
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 6f24f129a751..3caa2021a5d6 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -3746,8 +3746,8 @@  int cifs_setup_cifs_sb(struct smb_vol *pvolume_info,
 	return 0;
 }
 
-static void
-cleanup_volume_info_contents(struct smb_vol *volume_info)
+void
+cifs_cleanup_volume_info_contents(struct smb_vol *volume_info)
 {
 	kfree(volume_info->username);
 	kzfree(volume_info->password);
@@ -3762,10 +3762,136 @@  cifs_cleanup_volume_info(struct smb_vol *volume_info)
 {
 	if (!volume_info)
 		return;
-	cleanup_volume_info_contents(volume_info);
+	cifs_cleanup_volume_info_contents(volume_info);
 	kfree(volume_info);
 }
 
+/* Release all succeed connections */
+static inline void mount_put_conns(struct cifs_sb_info *cifs_sb,
+				   unsigned int xid,
+				   struct TCP_Server_Info *server,
+				   struct cifs_ses *ses, struct cifs_tcon *tcon)
+{
+	int rc = 0;
+
+	if (tcon)
+		cifs_put_tcon(tcon);
+	else if (ses)
+		cifs_put_smb_ses(ses);
+	else if (server)
+		cifs_put_tcp_session(server, 0);
+	cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS;
+	free_xid(xid);
+}
+
+/* Get connections for tcp, ses and tcon */
+static int mount_get_conns(struct smb_vol *vol, struct cifs_sb_info *cifs_sb,
+			   unsigned int *xid,
+			   struct TCP_Server_Info **nserver,
+			   struct cifs_ses **nses, struct cifs_tcon **ntcon)
+{
+	int rc = 0;
+	struct TCP_Server_Info *server;
+	struct cifs_ses *ses;
+	struct cifs_tcon *tcon;
+
+	*nserver = NULL;
+	*nses = NULL;
+	*ntcon = NULL;
+
+	*xid = get_xid();
+
+	/* get a reference to a tcp session */
+	server = cifs_get_tcp_session(vol);
+	if (IS_ERR(server)) {
+		rc = PTR_ERR(server);
+		return rc;
+	}
+
+	*nserver = server;
+
+	if ((vol->max_credits < 20) || (vol->max_credits > 60000))
+		server->max_credits = SMB2_MAX_CREDITS_AVAILABLE;
+	else
+		server->max_credits = vol->max_credits;
+
+	/* get a reference to a SMB session */
+	ses = cifs_get_smb_ses(server, vol);
+	if (IS_ERR(ses)) {
+		rc = PTR_ERR(ses);
+		return rc;
+	}
+
+	*nses = ses;
+
+	if ((vol->persistent == true) && (!(ses->server->capabilities &
+					    SMB2_GLOBAL_CAP_PERSISTENT_HANDLES))) {
+		cifs_dbg(VFS, "persistent handles not supported by server\n");
+		return -EOPNOTSUPP;
+	}
+
+	/* search for existing tcon to this server share */
+	tcon = cifs_get_tcon(ses, vol);
+	if (IS_ERR(tcon)) {
+		rc = PTR_ERR(tcon);
+		return rc;
+	}
+
+	*ntcon = tcon;
+
+	/* if new SMB3.11 POSIX extensions are supported do not remap / and \ */
+	if (tcon->posix_extensions)
+		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_POSIX_PATHS;
+
+	/* tell server which Unix caps we support */
+	if (cap_unix(tcon->ses)) {
+		/*
+		 * reset of caps checks mount to see if unix extensions disabled
+		 * for just this mount.
+		 */
+		reset_cifs_unix_caps(*xid, tcon, cifs_sb, vol);
+		if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) &&
+		    (le64_to_cpu(tcon->fsUnixInfo.Capability) &
+		     CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP))
+			return -EACCES;
+	} else
+		tcon->unix_ext = 0; /* server does not support them */
+
+	/* do not care if a following call succeed - informational */
+	if (!tcon->pipe && server->ops->qfs_tcon)
+		server->ops->qfs_tcon(*xid, tcon);
+
+	cifs_sb->wsize = server->ops->negotiate_wsize(tcon, vol);
+	cifs_sb->rsize = server->ops->negotiate_rsize(tcon, vol);
+
+	return 0;
+}
+
+static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
+			     struct cifs_tcon *tcon)
+{
+	struct tcon_link *tlink;
+
+	/* hang the tcon off of the superblock */
+	tlink = kzalloc(sizeof *tlink, GFP_KERNEL);
+	if (tlink == NULL)
+		return -ENOMEM;
+
+	tlink->tl_uid = ses->linux_uid;
+	tlink->tl_tcon = tcon;
+	tlink->tl_time = jiffies;
+	set_bit(TCON_LINK_MASTER, &tlink->tl_flags);
+	set_bit(TCON_LINK_IN_TREE, &tlink->tl_flags);
+
+	cifs_sb->master_tlink = tlink;
+	spin_lock(&cifs_sb->tlink_tree_lock);
+	tlink_rb_insert(&cifs_sb->tlink_tree, tlink);
+	spin_unlock(&cifs_sb->tlink_tree_lock);
+
+	queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks,
+				TLINK_IDLE_EXPIRE);
+	return 0;
+}
 
 #ifdef CONFIG_CIFS_DFS_UPCALL
 /*
@@ -3845,7 +3971,7 @@  expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
 			rc = PTR_ERR(mdata);
 			mdata = NULL;
 		} else {
-			cleanup_volume_info_contents(volume_info);
+			cifs_cleanup_volume_info_contents(volume_info);
 			rc = cifs_setup_volume_info(volume_info, mdata,
 							fake_devname, false);
 		}
@@ -3954,107 +4080,77 @@  cifs_are_all_path_components_accessible(struct TCP_Server_Info *server,
 	return rc;
 }
 
-int
-cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
+/*
+ * Check if path is remote (e.g. a DFS share). Return -EREMOTE if it is,
+ * otherwise 0.
+ */
+static int is_path_remote(struct cifs_sb_info *cifs_sb, struct smb_vol *vol,
+			  const unsigned int xid,
+			  struct TCP_Server_Info *server,
+			  struct cifs_tcon *tcon)
 {
 	int rc;
-	unsigned int xid;
-	struct cifs_ses *ses;
-	struct cifs_tcon *tcon;
-	struct TCP_Server_Info *server;
-	char   *full_path;
-	struct tcon_link *tlink;
-#ifdef CONFIG_CIFS_DFS_UPCALL
-	int referral_walks_count = 0;
-#endif
-
-#ifdef CONFIG_CIFS_DFS_UPCALL
-try_mount_again:
-	/* cleanup activities if we're chasing a referral */
-	if (referral_walks_count) {
-		if (tcon)
-			cifs_put_tcon(tcon);
-		else if (ses)
-			cifs_put_smb_ses(ses);
-
-		cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS;
+	char *full_path;
 
-		free_xid(xid);
-	}
-#endif
-	rc = 0;
-	tcon = NULL;
-	ses = NULL;
-	server = NULL;
-	full_path = NULL;
-	tlink = NULL;
+	if (!server->ops->is_path_accessible)
+		return -ENOSYS;
 
-	xid = get_xid();
+	/*
+	 * cifs_build_path_to_root works only when we have a valid tcon
+	 */
+	full_path = cifs_build_path_to_root(vol, cifs_sb, tcon,
+					    tcon->Flags & SMB_SHARE_IS_IN_DFS);
+	if (full_path == NULL)
+		return -ENOMEM;
 
-	/* get a reference to a tcp session */
-	server = cifs_get_tcp_session(volume_info);
-	if (IS_ERR(server)) {
-		rc = PTR_ERR(server);
-		goto out;
-	}
-	if ((volume_info->max_credits < 20) ||
-	     (volume_info->max_credits > 60000))
-		server->max_credits = SMB2_MAX_CREDITS_AVAILABLE;
-	else
-		server->max_credits = volume_info->max_credits;
-	/* get a reference to a SMB session */
-	ses = cifs_get_smb_ses(server, volume_info);
-	if (IS_ERR(ses)) {
-		rc = PTR_ERR(ses);
-		ses = NULL;
-		goto mount_fail_check;
-	}
+	cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path);
 
-	if ((volume_info->persistent == true) && ((ses->server->capabilities &
-		SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) == 0)) {
-		cifs_dbg(VFS, "persistent handles not supported by server\n");
-		rc = -EOPNOTSUPP;
-		goto mount_fail_check;
+	rc = server->ops->is_path_accessible(xid, tcon, cifs_sb,
+					     full_path);
+	if (rc != 0 && rc != -EREMOTE) {
+		kfree(full_path);
+		return rc;
 	}
 
-	/* search for existing tcon to this server share */
-	tcon = cifs_get_tcon(ses, volume_info);
-	if (IS_ERR(tcon)) {
-		rc = PTR_ERR(tcon);
-		tcon = NULL;
-		if (rc == -EACCES)
-			goto mount_fail_check;
-
-		goto remote_path_check;
+	if (rc != -EREMOTE) {
+		rc = cifs_are_all_path_components_accessible(server, xid, tcon,
+							     cifs_sb,
+							     full_path);
+		if (rc != 0) {
+			cifs_dbg(VFS, "cannot query dirs between root and final path, "
+				 "enabling CIFS_MOUNT_USE_PREFIX_PATH\n");
+			cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
+			rc = 0;
+		}
 	}
 
-	/* if new SMB3.11 POSIX extensions are supported do not remap / and \ */
-	if (tcon->posix_extensions)
-		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_POSIX_PATHS;
+	kfree(full_path);
+	return rc;
+}
 
-	/* tell server which Unix caps we support */
-	if (cap_unix(tcon->ses)) {
-		/* reset of caps checks mount to see if unix extensions
-		   disabled for just this mount */
-		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 */
+#ifdef CONFIG_CIFS_DFS_UPCALL
+int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
+{
+	int rc = 0;
+	unsigned int xid;
+	struct cifs_ses *ses;
+	struct cifs_tcon *tcon = NULL;
+	struct TCP_Server_Info *server;
+	char *old_mountdata;
+	int count;
 
-	/* do not care if a following call succeed - informational */
-	if (!tcon->pipe && server->ops->qfs_tcon)
-		server->ops->qfs_tcon(xid, tcon);
+	rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
+	if (!rc && tcon) {
+		rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
+		if (!rc)
+			goto out;
+		if (rc != -EREMOTE)
+			goto error;
+	}
+	if (rc == -EACCES || rc == -EOPNOTSUPP)
+		goto error;
 
-	cifs_sb->wsize = server->ops->negotiate_wsize(tcon, volume_info);
-	cifs_sb->rsize = server->ops->negotiate_rsize(tcon, volume_info);
 
-remote_path_check:
-#ifdef CONFIG_CIFS_DFS_UPCALL
 	/*
 	 * Perform an unconditional check for whether there are DFS
 	 * referrals for this path without prefix, to provide support
@@ -4062,119 +4158,100 @@  cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
 	 * 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, ses, volume_info, cifs_sb,
-						false);
-		if (!refrc) {
-			referral_walks_count++;
-			goto try_mount_again;
-		}
+	old_mountdata = cifs_sb->mountdata;
+	(void)expand_dfs_referral(xid, ses, vol, cifs_sb, false);
+
+	if (cifs_sb->mountdata == NULL) {
+		rc = -ENOENT;
+		goto error;
 	}
-#endif
 
-	/* check if a whole path is not remote */
-	if (!rc && tcon) {
-		if (!server->ops->is_path_accessible) {
-			rc = -ENOSYS;
-			goto mount_fail_check;
+	if (cifs_sb->mountdata != old_mountdata) {
+		/* If we were redirected, reconnect to new target server */
+		mount_put_conns(cifs_sb, xid, server, ses, tcon);
+		rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
+	}
+	if (rc) {
+		if (rc == -EACCES || rc == -EOPNOTSUPP)
+			goto error;
+	}
+
+	for (count = 1; ;) {
+		if (!rc && tcon) {
+			rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
+			if (!rc || rc != -EREMOTE)
+				break;
 		}
 		/*
-		 * cifs_build_path_to_root works only when we have a valid tcon
+		 * BB: when we implement proper loop detection,
+		 *     we will remove this check. But now we need it
+		 *     to prevent an indefinite loop if 'DFS tree' is
+		 *     misconfigured (i.e. has loops).
 		 */
-		full_path = cifs_build_path_to_root(volume_info, cifs_sb, tcon,
-					tcon->Flags & SMB_SHARE_IS_IN_DFS);
-		if (full_path == NULL) {
-			rc = -ENOMEM;
-			goto mount_fail_check;
-		}
-		rc = server->ops->is_path_accessible(xid, tcon, cifs_sb,
-						     full_path);
-		if (rc != 0 && rc != -EREMOTE) {
-			kfree(full_path);
-			goto mount_fail_check;
-		}
-
-		if (rc != -EREMOTE) {
-			rc = cifs_are_all_path_components_accessible(server,
-							     xid, tcon, cifs_sb,
-							     full_path);
-			if (rc != 0) {
-				cifs_dbg(VFS, "cannot query dirs between root and final path, "
-					 "enabling CIFS_MOUNT_USE_PREFIX_PATH\n");
-				cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
-				rc = 0;
-			}
-		}
-		kfree(full_path);
-	}
-
-	/* get referral if needed */
-	if (rc == -EREMOTE) {
-#ifdef CONFIG_CIFS_DFS_UPCALL
-		if (referral_walks_count > MAX_NESTED_LINKS) {
-			/*
-			 * BB: when we implement proper loop detection,
-			 *     we will remove this check. But now we need it
-			 *     to prevent an indefinite loop if 'DFS tree' is
-			 *     misconfigured (i.e. has loops).
-			 */
+		if (count++ > MAX_NESTED_LINKS) {
 			rc = -ELOOP;
-			goto mount_fail_check;
+			break;
 		}
 
-		rc = expand_dfs_referral(xid, ses, volume_info, cifs_sb, true);
+		old_mountdata = cifs_sb->mountdata;
+		rc = expand_dfs_referral(xid, tcon->ses, vol, cifs_sb,
+					 true);
+		if (rc)
+			break;
 
-		if (!rc) {
-			referral_walks_count++;
-			goto try_mount_again;
+		if (cifs_sb->mountdata != old_mountdata) {
+			mount_put_conns(cifs_sb, xid, server, ses, tcon);
+			rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses,
+					     &tcon);
+		}
+		if (rc) {
+			if (rc == -EACCES || rc == -EOPNOTSUPP || !server ||
+			    !ses)
+				goto error;
 		}
-		goto mount_fail_check;
-#else /* No DFS support, return error on mount */
-		rc = -EOPNOTSUPP;
-#endif
 	}
 
 	if (rc)
-		goto mount_fail_check;
-
-	/* now, hang the tcon off of the superblock */
-	tlink = kzalloc(sizeof *tlink, GFP_KERNEL);
-	if (tlink == NULL) {
-		rc = -ENOMEM;
-		goto mount_fail_check;
-	}
+		goto error;
 
-	tlink->tl_uid = ses->linux_uid;
-	tlink->tl_tcon = tcon;
-	tlink->tl_time = jiffies;
-	set_bit(TCON_LINK_MASTER, &tlink->tl_flags);
-	set_bit(TCON_LINK_IN_TREE, &tlink->tl_flags);
+out:
+	free_xid(xid);
+	return mount_setup_tlink(cifs_sb, ses, tcon);
 
-	cifs_sb->master_tlink = tlink;
-	spin_lock(&cifs_sb->tlink_tree_lock);
-	tlink_rb_insert(&cifs_sb->tlink_tree, tlink);
-	spin_unlock(&cifs_sb->tlink_tree_lock);
+error:
+	mount_put_conns(cifs_sb, xid, server, ses, tcon);
+	return rc;
+}
+#else
+int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
+{
+	int rc = 0;
+	unsigned int xid;
+	struct cifs_ses *ses;
+	struct cifs_tcon *tcon;
+	struct TCP_Server_Info *server;
 
-	queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks,
-				TLINK_IDLE_EXPIRE);
+	rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
+	if (rc)
+		goto error;
 
-mount_fail_check:
-	/* on error free sesinfo and tcon struct if needed */
-	if (rc) {
-		/* If find_unc succeeded then rc == 0 so we can not end */
-		/* up accidentally freeing someone elses tcon struct */
-		if (tcon)
-			cifs_put_tcon(tcon);
-		else if (ses)
-			cifs_put_smb_ses(ses);
-		else
-			cifs_put_tcp_session(server, 0);
+	if (tcon) {
+		rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
+		if (rc == -EREMOTE)
+			rc = -EOPNOTSUPP;
+		if (rc)
+			goto error;
 	}
 
-out:
 	free_xid(xid);
+
+	return mount_setup_tlink(cifs_sb, ses, tcon);
+
+error:
+	mount_put_conns(cifs_sb, xid, server, ses, tcon);
 	return rc;
 }
+#endif
 
 /*
  * Issue a TREE_CONNECT request.