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: 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>
This commit is contained in:
parent
ffdec8d642
commit
bacd704a95
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user