Commit bacd704a authored by Paulo Alcantara (SUSE)'s avatar Paulo Alcantara (SUSE) Committed by Steve French
Browse files

cifs: handle prefix paths in reconnect



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: default avatarPaulo Alcantara (SUSE) <pc@cjr.nz>
Acked-by: default avatarRonnie Sahlberg <lsahlber@redhat.com>
Reviewed-by: default avatarAurelien Aptel <aaptel@suse.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent ffdec8d6
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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,
+15 −4
Original line number Diff line number Diff line
@@ -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;
	}
+12 −51
Original line number Diff line number Diff line
@@ -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);
+38 −0
Original line number Diff line number Diff line
@@ -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)
{
+4 −0
Original line number Diff line number Diff line
@@ -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)
Loading