cifs: handle prefix paths in reconnect
authorPaulo Alcantara (SUSE) <pc@cjr.nz>
Thu, 20 Feb 2020 22:49:34 +0000 (19:49 -0300)
committerSteve French <stfrench@microsoft.com>
Mon, 23 Mar 2020 03:49:09 +0000 (22:49 -0500)
For the case where we have a DFS path like below and we're currently
connected to targetA:

    //dfsroot/link -> //targetA/share/foo, //targetB/share/bar

after failover, we should make sure to update cifs_sb->prepath so the
next operations will use the new prefix path "/bar".

Besides, in order to simplify the use of different prefix paths,
enforce CIFS_MOUNT_USE_PREFIX_PATH for DFS mounts so we don't have to
revalidate the root dentry every time we set a new prefix path.

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Acked-by: Ronnie Sahlberg <lsahlber@redhat.com>
Reviewed-by: Aurelien Aptel <aaptel@suse.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/cifsproto.h
fs/cifs/cifssmb.c
fs/cifs/connect.c
fs/cifs/dfs_cache.c
fs/cifs/dfs_cache.h
fs/cifs/misc.c
fs/cifs/smb2pdu.c

index e5cb681..12a895e 100644 (file)
@@ -602,6 +602,11 @@ int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov,
                               int resp_buftype,
                               struct cifs_search_info *srch_inf);
 
+struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server);
+void cifs_put_tcp_super(struct super_block *sb);
+int update_super_prepath(struct cifs_tcon *tcon, const char *prefix,
+                        size_t prefix_len);
+
 #ifdef CONFIG_CIFS_DFS_UPCALL
 static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses,
                               const char *old_path,
index 6f6fb36..6e95ee6 100644 (file)
@@ -162,9 +162,18 @@ static int __cifs_reconnect_tcon(const struct nls_table *nlsc,
 
        for (it = dfs_cache_get_tgt_iterator(&tl); it;
             it = dfs_cache_get_next_tgt(&tl, it)) {
-               const char *tgt = dfs_cache_get_tgt_name(it);
+               const char *share, *prefix;
+               size_t share_len, prefix_len;
 
-               extract_unc_hostname(tgt, &dfs_host, &dfs_host_len);
+               rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix,
+                                            &prefix_len);
+               if (rc) {
+                       cifs_dbg(VFS, "%s: failed to parse target share %d\n",
+                                __func__, rc);
+                       continue;
+               }
+
+               extract_unc_hostname(share, &dfs_host, &dfs_host_len);
 
                if (dfs_host_len != tcp_host_len
                    || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
@@ -175,11 +184,13 @@ static int __cifs_reconnect_tcon(const struct nls_table *nlsc,
                        continue;
                }
 
-               scnprintf(tree, MAX_TREE_SIZE, "\\%s", tgt);
+               scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, share);
 
                rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc);
-               if (!rc)
+               if (!rc) {
+                       rc = update_super_prepath(tcon, prefix, prefix_len);
                        break;
+               }
                if (rc == -EREMOTE)
                        break;
        }
index 4804d1d..e2196b3 100644 (file)
@@ -57,7 +57,6 @@
 #include "smb2proto.h"
 #include "smbdirect.h"
 #include "dns_resolve.h"
-#include "cifsfs.h"
 #ifdef CONFIG_CIFS_DFS_UPCALL
 #include "dfs_cache.h"
 #endif
@@ -389,54 +388,7 @@ static inline int reconn_set_ipaddr(struct TCP_Server_Info *server)
 #endif
 
 #ifdef CONFIG_CIFS_DFS_UPCALL
-struct super_cb_data {
-       struct TCP_Server_Info *server;
-       struct super_block *sb;
-};
-
 /* These functions must be called with server->srv_mutex held */
-
-static void super_cb(struct super_block *sb, void *arg)
-{
-       struct super_cb_data *d = arg;
-       struct cifs_sb_info *cifs_sb;
-       struct cifs_tcon *tcon;
-
-       if (d->sb)
-               return;
-
-       cifs_sb = CIFS_SB(sb);
-       tcon = cifs_sb_master_tcon(cifs_sb);
-       if (tcon->ses->server == d->server)
-               d->sb = sb;
-}
-
-static struct super_block *get_tcp_super(struct TCP_Server_Info *server)
-{
-       struct super_cb_data d = {
-               .server = server,
-               .sb = NULL,
-       };
-
-       iterate_supers_type(&cifs_fs_type, super_cb, &d);
-
-       if (unlikely(!d.sb))
-               return ERR_PTR(-ENOENT);
-       /*
-        * Grab an active reference in order to prevent automounts (DFS links)
-        * of expiring and then freeing up our cifs superblock pointer while
-        * we're doing failover.
-        */
-       cifs_sb_active(d.sb);
-       return d.sb;
-}
-
-static inline void put_tcp_super(struct super_block *sb)
-{
-       if (!IS_ERR_OR_NULL(sb))
-               cifs_sb_deactive(sb);
-}
-
 static void reconn_inval_dfs_target(struct TCP_Server_Info *server,
                                    struct cifs_sb_info *cifs_sb,
                                    struct dfs_cache_tgt_list *tgt_list,
@@ -508,7 +460,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
        server->nr_targets = 1;
 #ifdef CONFIG_CIFS_DFS_UPCALL
        spin_unlock(&GlobalMid_Lock);
-       sb = get_tcp_super(server);
+       sb = cifs_get_tcp_super(server);
        if (IS_ERR(sb)) {
                rc = PTR_ERR(sb);
                cifs_dbg(FYI, "%s: will not do DFS failover: rc = %d\n",
@@ -535,7 +487,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
                spin_unlock(&GlobalMid_Lock);
 #ifdef CONFIG_CIFS_DFS_UPCALL
                dfs_cache_free_tgts(&tgt_list);
-               put_tcp_super(sb);
+               cifs_put_tcp_super(sb);
 #endif
                return rc;
        } else
@@ -666,7 +618,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
 
        }
 
-       put_tcp_super(sb);
+       cifs_put_tcp_super(sb);
 #endif
        if (server->tcpStatus == CifsNeedNegotiate)
                mod_delayed_work(cifsiod_wq, &server->echo, 0);
@@ -4999,6 +4951,15 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
         * dentry revalidation to think the dentry are stale (ESTALE).
         */
        cifs_autodisable_serverino(cifs_sb);
+       /*
+        * Force the use of prefix path to support failover on DFS paths that
+        * resolve to targets that have different prefix paths.
+        */
+       cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
+       kfree(cifs_sb->prepath);
+       cifs_sb->prepath = vol->prepath;
+       vol->prepath = NULL;
+
 out:
        free_xid(xid);
        cifs_try_adding_channels(ses);
index 43c1b43..a67f88b 100644 (file)
@@ -1260,6 +1260,44 @@ void dfs_cache_del_vol(const char *fullpath)
        kref_put(&vi->refcnt, vol_release);
 }
 
+/**
+ * dfs_cache_get_tgt_share - parse a DFS target
+ *
+ * @it: DFS target iterator.
+ * @share: tree name.
+ * @share_len: length of tree name.
+ * @prefix: prefix path.
+ * @prefix_len: length of prefix path.
+ *
+ * Return zero if target was parsed correctly, otherwise non-zero.
+ */
+int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it,
+                           const char **share, size_t *share_len,
+                           const char **prefix, size_t *prefix_len)
+{
+       char *s, sep;
+
+       if (!it || !share || !share_len || !prefix || !prefix_len)
+               return -EINVAL;
+
+       sep = it->it_name[0];
+       if (sep != '\\' && sep != '/')
+               return -EINVAL;
+
+       s = strchr(it->it_name + 1, sep);
+       if (!s)
+               return -EINVAL;
+
+       s = strchrnul(s + 1, sep);
+
+       *share = it->it_name;
+       *share_len = s - it->it_name;
+       *prefix = *s ? s + 1 : s;
+       *prefix_len = &it->it_name[strlen(it->it_name)] - *prefix;
+
+       return 0;
+}
+
 /* Get all tcons that are within a DFS namespace and can be refreshed */
 static void get_tcons(struct TCP_Server_Info *server, struct list_head *head)
 {
index 99ee44f..bf94d08 100644 (file)
@@ -49,6 +49,10 @@ extern int dfs_cache_update_vol(const char *fullpath,
                                struct TCP_Server_Info *server);
 extern void dfs_cache_del_vol(const char *fullpath);
 
+extern int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it,
+                                  const char **share, size_t *share_len,
+                                  const char **prefix, size_t *prefix_len);
+
 static inline struct dfs_cache_tgt_iterator *
 dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl,
                       struct dfs_cache_tgt_iterator *it)
index 40ca394..a456feb 100644 (file)
@@ -31,6 +31,7 @@
 #include "nterr.h"
 #include "cifs_unicode.h"
 #include "smb2pdu.h"
+#include "cifsfs.h"
 
 extern mempool_t *cifs_sm_req_poolp;
 extern mempool_t *cifs_req_poolp;
@@ -1022,3 +1023,82 @@ int copy_path_name(char *dst, const char *src)
        name_len++;
        return name_len;
 }
+
+struct super_cb_data {
+       struct TCP_Server_Info *server;
+       struct super_block *sb;
+};
+
+static void super_cb(struct super_block *sb, void *arg)
+{
+       struct super_cb_data *d = arg;
+       struct cifs_sb_info *cifs_sb;
+       struct cifs_tcon *tcon;
+
+       if (d->sb)
+               return;
+
+       cifs_sb = CIFS_SB(sb);
+       tcon = cifs_sb_master_tcon(cifs_sb);
+       if (tcon->ses->server == d->server)
+               d->sb = sb;
+}
+
+struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server)
+{
+       struct super_cb_data d = {
+               .server = server,
+               .sb = NULL,
+       };
+
+       iterate_supers_type(&cifs_fs_type, super_cb, &d);
+
+       if (unlikely(!d.sb))
+               return ERR_PTR(-ENOENT);
+       /*
+        * Grab an active reference in order to prevent automounts (DFS links)
+        * of expiring and then freeing up our cifs superblock pointer while
+        * we're doing failover.
+        */
+       cifs_sb_active(d.sb);
+       return d.sb;
+}
+
+void cifs_put_tcp_super(struct super_block *sb)
+{
+       if (!IS_ERR_OR_NULL(sb))
+               cifs_sb_deactive(sb);
+}
+
+int update_super_prepath(struct cifs_tcon *tcon, const char *prefix,
+                        size_t prefix_len)
+{
+       struct super_block *sb;
+       struct cifs_sb_info *cifs_sb;
+       int rc = 0;
+
+       sb = cifs_get_tcp_super(tcon->ses->server);
+       if (IS_ERR(sb))
+               return PTR_ERR(sb);
+
+       cifs_sb = CIFS_SB(sb);
+
+       kfree(cifs_sb->prepath);
+
+       if (*prefix && prefix_len) {
+               cifs_sb->prepath = kstrndup(prefix, prefix_len, GFP_ATOMIC);
+               if (!cifs_sb->prepath) {
+                       rc = -ENOMEM;
+                       goto out;
+               }
+
+               convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb));
+       } else
+               cifs_sb->prepath = NULL;
+
+       cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
+
+out:
+       cifs_put_tcp_super(sb);
+       return rc;
+}
index 28c0be5..8c23c10 100644 (file)
@@ -193,9 +193,18 @@ static int __smb2_reconnect(const struct nls_table *nlsc,
 
        for (it = dfs_cache_get_tgt_iterator(&tl); it;
             it = dfs_cache_get_next_tgt(&tl, it)) {
-               const char *tgt = dfs_cache_get_tgt_name(it);
+               const char *share, *prefix;
+               size_t share_len, prefix_len;
 
-               extract_unc_hostname(tgt, &dfs_host, &dfs_host_len);
+               rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix,
+                                            &prefix_len);
+               if (rc) {
+                       cifs_dbg(VFS, "%s: failed to parse target share %d\n",
+                                __func__, rc);
+                       continue;
+               }
+
+               extract_unc_hostname(share, &dfs_host, &dfs_host_len);
 
                if (dfs_host_len != tcp_host_len
                    || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
@@ -206,11 +215,13 @@ static int __smb2_reconnect(const struct nls_table *nlsc,
                        continue;
                }
 
-               scnprintf(tree, MAX_TREE_SIZE, "\\%s", tgt);
+               scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, share);
 
                rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc);
-               if (!rc)
+               if (!rc) {
+                       rc = update_super_prepath(tcon, prefix, prefix_len);
                        break;
+               }
                if (rc == -EREMOTE)
                        break;
        }