Commit c88f7dcd authored by Paulo Alcantara's avatar Paulo Alcantara Committed by Steve French

cifs: support nested dfs links over reconnect

Mounting a dfs link that has nested links was already supported at
mount(2), so make it work over reconnect as well.

Make the following case work:

* mount //root/dfs/link /mnt -o ...
  - final share: /server/share

* in server settings
  - change target folder of /root/dfs/link3 to /server/share2
  - change target folder of /root/dfs/link2 to /root/dfs/link3
  - change target folder of /root/dfs/link to /root/dfs/link2

* mount -o remount,... /mnt
 - refresh all dfs referrals
 - mark current connection for failover
 - cifs_reconnect() reconnects to root server
 - tree_connect()
   * checks that /root/dfs/link2 is a link, then chase it
   * checks that root/dfs/link3 is a link, then chase it
   * finally tree connect to /server/share2

If the mounted share is no longer accessible and a reconnect had been
triggered, the client will retry it from both last referral
path (/root/dfs/link3) and original referral path (/root/dfs/link).

Any new referral paths found while chasing dfs links over reconnect,
it will be updated to TCP_Server_Info::leaf_fullpath, accordingly.
Signed-off-by: default avatarPaulo Alcantara (SUSE) <pc@cjr.nz>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 71e6864e
...@@ -307,12 +307,8 @@ static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt, ...@@ -307,12 +307,8 @@ static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt,
static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
{ {
struct cifs_sb_info *cifs_sb; struct cifs_sb_info *cifs_sb;
struct cifs_ses *ses;
struct cifs_tcon *tcon;
void *page; void *page;
char *full_path, *root_path; char *full_path;
unsigned int xid;
int rc;
struct vfsmount *mnt; struct vfsmount *mnt;
cifs_dbg(FYI, "in %s\n", __func__); cifs_dbg(FYI, "in %s\n", __func__);
...@@ -324,8 +320,6 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) ...@@ -324,8 +320,6 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
* the double backslashes usually used in the UNC. This function * the double backslashes usually used in the UNC. This function
* gives us the latter, so we must adjust the result. * gives us the latter, so we must adjust the result.
*/ */
mnt = ERR_PTR(-ENOMEM);
cifs_sb = CIFS_SB(mntpt->d_sb); cifs_sb = CIFS_SB(mntpt->d_sb);
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) { if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) {
mnt = ERR_PTR(-EREMOTE); mnt = ERR_PTR(-EREMOTE);
...@@ -341,60 +335,11 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt) ...@@ -341,60 +335,11 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
} }
convert_delimiter(full_path, '\\'); convert_delimiter(full_path, '\\');
cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path); cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path);
if (!cifs_sb_master_tlink(cifs_sb)) {
cifs_dbg(FYI, "%s: master tlink is NULL\n", __func__);
goto free_full_path;
}
tcon = cifs_sb_master_tcon(cifs_sb);
if (!tcon) {
cifs_dbg(FYI, "%s: master tcon is NULL\n", __func__);
goto free_full_path;
}
root_path = kstrdup(tcon->treeName, GFP_KERNEL);
if (!root_path) {
mnt = ERR_PTR(-ENOMEM);
goto free_full_path;
}
cifs_dbg(FYI, "%s: root path: %s\n", __func__, root_path);
ses = tcon->ses;
xid = get_xid();
/*
* If DFS root has been expired, then unconditionally fetch it again to
* refresh DFS referral cache.
*/
rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
root_path + 1, NULL, NULL);
if (!rc) {
rc = dfs_cache_find(xid, ses, cifs_sb->local_nls,
cifs_remap(cifs_sb), full_path + 1,
NULL, NULL);
}
free_xid(xid);
if (rc) {
mnt = ERR_PTR(rc);
goto free_root_path;
}
/*
* OK - we were able to get and cache a referral for @full_path.
*
* Now, pass it down to cifs_mount() and it will retry every available
* node server in case of failures - no need to do it here.
*/
mnt = cifs_dfs_do_mount(mntpt, cifs_sb, full_path); mnt = cifs_dfs_do_mount(mntpt, cifs_sb, full_path);
cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, full_path + 1, mnt);
full_path + 1, mnt);
free_root_path:
kfree(root_path);
free_full_path: free_full_path:
free_dentry_path(page); free_dentry_path(page);
cdda_exit: cdda_exit:
......
...@@ -61,11 +61,6 @@ struct cifs_sb_info { ...@@ -61,11 +61,6 @@ struct cifs_sb_info {
/* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */ /* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */
char *prepath; char *prepath;
/*
* Canonical DFS path initially provided by the mount call. We might connect to something
* different via DFS but we want to keep it to do failover properly.
*/
char *origin_fullpath; /* \\HOST\SHARE\[OPTIONAL PATH] */
/* randomly generated 128-bit number for indexing dfs mount groups in referral cache */ /* randomly generated 128-bit number for indexing dfs mount groups in referral cache */
uuid_t dfs_mount_id; uuid_t dfs_mount_id;
/* /*
......
...@@ -697,6 +697,19 @@ struct TCP_Server_Info { ...@@ -697,6 +697,19 @@ struct TCP_Server_Info {
#endif #endif
#ifdef CONFIG_CIFS_DFS_UPCALL #ifdef CONFIG_CIFS_DFS_UPCALL
bool is_dfs_conn; /* if a dfs connection */ bool is_dfs_conn; /* if a dfs connection */
struct mutex refpath_lock; /* protects leaf_fullpath */
/*
* Canonical DFS full paths that were used to chase referrals in mount and reconnect.
*
* origin_fullpath: first or original referral path
* leaf_fullpath: last referral path (might be changed due to nested links in reconnect)
*
* current_fullpath: pointer to either origin_fullpath or leaf_fullpath
* NOTE: cannot be accessed outside cifs_reconnect() and smb2_reconnect()
*
* format: \\HOST\SHARE\[OPTIONAL PATH]
*/
char *origin_fullpath, *leaf_fullpath, *current_fullpath;
#endif #endif
}; };
...@@ -1097,7 +1110,6 @@ struct cifs_tcon { ...@@ -1097,7 +1110,6 @@ struct cifs_tcon {
struct cached_fid crfid; /* Cached root fid */ struct cached_fid crfid; /* Cached root fid */
/* BB add field for back pointer to sb struct(s)? */ /* BB add field for back pointer to sb struct(s)? */
#ifdef CONFIG_CIFS_DFS_UPCALL #ifdef CONFIG_CIFS_DFS_UPCALL
char *dfs_path; /* canonical DFS path */
struct list_head ulist; /* cache update list */ struct list_head ulist; /* cache update list */
#endif #endif
}; };
...@@ -1948,4 +1960,14 @@ static inline bool is_tcon_dfs(struct cifs_tcon *tcon) ...@@ -1948,4 +1960,14 @@ static inline bool is_tcon_dfs(struct cifs_tcon *tcon)
tcon->share_flags & (SHI1005_FLAGS_DFS | SHI1005_FLAGS_DFS_ROOT); tcon->share_flags & (SHI1005_FLAGS_DFS | SHI1005_FLAGS_DFS_ROOT);
} }
static inline bool cifs_is_referral_server(struct cifs_tcon *tcon,
const struct dfs_info3_param *ref)
{
/*
* Check if all targets are capable of handling DFS referrals as per
* MS-DFSC 2.2.4 RESP_GET_DFS_REFERRAL.
*/
return is_tcon_dfs(tcon) || (ref && (ref->flags & DFSREF_REFERRAL_SERVER));
}
#endif /* _CIFS_GLOB_H */ #endif /* _CIFS_GLOB_H */
...@@ -607,7 +607,7 @@ int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov, ...@@ -607,7 +607,7 @@ int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov,
struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server); struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server);
void cifs_put_tcp_super(struct super_block *sb); void cifs_put_tcp_super(struct super_block *sb);
int update_super_prepath(struct cifs_tcon *tcon, char *prefix); int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix);
char *extract_hostname(const char *unc); char *extract_hostname(const char *unc);
char *extract_sharename(const char *unc); char *extract_sharename(const char *unc);
...@@ -634,4 +634,7 @@ static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int options) ...@@ -634,4 +634,7 @@ static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int options)
return options; return options;
} }
struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon);
void cifs_put_tcon_super(struct super_block *sb);
#endif /* _CIFSPROTO_H */ #endif /* _CIFSPROTO_H */
This diff is collapsed.
...@@ -1364,9 +1364,9 @@ static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cach ...@@ -1364,9 +1364,9 @@ static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cach
} }
/* Refresh dfs referral of tcon and mark it for reconnect if needed */ /* Refresh dfs referral of tcon and mark it for reconnect if needed */
static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh) static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct cifs_tcon *tcon,
bool force_refresh)
{ {
const char *path = tcon->dfs_path + 1;
struct cifs_ses *ses; struct cifs_ses *ses;
struct cache_entry *ce; struct cache_entry *ce;
struct dfs_info3_param *refs = NULL; struct dfs_info3_param *refs = NULL;
...@@ -1422,6 +1422,20 @@ static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool ...@@ -1422,6 +1422,20 @@ static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool
return rc; return rc;
} }
static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh)
{
struct TCP_Server_Info *server = tcon->ses->server;
mutex_lock(&server->refpath_lock);
if (strcasecmp(server->leaf_fullpath, server->origin_fullpath))
__refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, force_refresh);
mutex_unlock(&server->refpath_lock);
__refresh_tcon(server->origin_fullpath + 1, sessions, tcon, force_refresh);
return 0;
}
/** /**
* dfs_cache_remount_fs - remount a DFS share * dfs_cache_remount_fs - remount a DFS share
* *
...@@ -1435,6 +1449,7 @@ static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool ...@@ -1435,6 +1449,7 @@ static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb) int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
{ {
struct cifs_tcon *tcon; struct cifs_tcon *tcon;
struct TCP_Server_Info *server;
struct mount_group *mg; struct mount_group *mg;
struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL}; struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
int rc; int rc;
...@@ -1443,13 +1458,15 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb) ...@@ -1443,13 +1458,15 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
return -EINVAL; return -EINVAL;
tcon = cifs_sb_master_tcon(cifs_sb); tcon = cifs_sb_master_tcon(cifs_sb);
if (!tcon->dfs_path) { server = tcon->ses->server;
cifs_dbg(FYI, "%s: not a dfs tcon\n", __func__);
if (!server->origin_fullpath) {
cifs_dbg(FYI, "%s: not a dfs mount\n", __func__);
return 0; return 0;
} }
if (uuid_is_null(&cifs_sb->dfs_mount_id)) { if (uuid_is_null(&cifs_sb->dfs_mount_id)) {
cifs_dbg(FYI, "%s: tcon has no dfs mount group id\n", __func__); cifs_dbg(FYI, "%s: no dfs mount group id\n", __func__);
return -EINVAL; return -EINVAL;
} }
...@@ -1457,7 +1474,7 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb) ...@@ -1457,7 +1474,7 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
mg = find_mount_group_locked(&cifs_sb->dfs_mount_id); mg = find_mount_group_locked(&cifs_sb->dfs_mount_id);
if (IS_ERR(mg)) { if (IS_ERR(mg)) {
mutex_unlock(&mount_group_list_lock); mutex_unlock(&mount_group_list_lock);
cifs_dbg(FYI, "%s: tcon has ipc session to refresh referral\n", __func__); cifs_dbg(FYI, "%s: no ipc session for refreshing referral\n", __func__);
return PTR_ERR(mg); return PTR_ERR(mg);
} }
kref_get(&mg->refcount); kref_get(&mg->refcount);
...@@ -1498,9 +1515,12 @@ static void refresh_mounts(struct cifs_ses **sessions) ...@@ -1498,9 +1515,12 @@ static void refresh_mounts(struct cifs_ses **sessions)
spin_lock(&cifs_tcp_ses_lock); spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
if (!server->is_dfs_conn)
continue;
list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
if (tcon->dfs_path) { if (!tcon->ipc && !tcon->need_reconnect) {
tcon->tc_count++; tcon->tc_count++;
list_add_tail(&tcon->ulist, &tcons); list_add_tail(&tcon->ulist, &tcons);
} }
...@@ -1510,8 +1530,16 @@ static void refresh_mounts(struct cifs_ses **sessions) ...@@ -1510,8 +1530,16 @@ static void refresh_mounts(struct cifs_ses **sessions)
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) { list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
struct TCP_Server_Info *server = tcon->ses->server;
list_del_init(&tcon->ulist); list_del_init(&tcon->ulist);
refresh_tcon(sessions, tcon, false);
mutex_lock(&server->refpath_lock);
if (strcasecmp(server->leaf_fullpath, server->origin_fullpath))
__refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, false);
mutex_unlock(&server->refpath_lock);
__refresh_tcon(server->origin_fullpath + 1, sessions, tcon, false);
cifs_put_tcon(tcon); cifs_put_tcon(tcon);
} }
} }
......
...@@ -139,9 +139,6 @@ tconInfoFree(struct cifs_tcon *buf_to_free) ...@@ -139,9 +139,6 @@ tconInfoFree(struct cifs_tcon *buf_to_free)
kfree(buf_to_free->nativeFileSystem); kfree(buf_to_free->nativeFileSystem);
kfree_sensitive(buf_to_free->password); kfree_sensitive(buf_to_free->password);
kfree(buf_to_free->crfid.fid); kfree(buf_to_free->crfid.fid);
#ifdef CONFIG_CIFS_DFS_UPCALL
kfree(buf_to_free->dfs_path);
#endif
kfree(buf_to_free); kfree(buf_to_free);
} }
...@@ -1288,69 +1285,20 @@ int match_target_ip(struct TCP_Server_Info *server, ...@@ -1288,69 +1285,20 @@ int match_target_ip(struct TCP_Server_Info *server,
return rc; return rc;
} }
static void tcon_super_cb(struct super_block *sb, void *arg) int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix)
{ {
struct super_cb_data *sd = arg;
struct cifs_tcon *tcon = sd->data;
struct cifs_sb_info *cifs_sb;
if (sd->sb)
return;
cifs_sb = CIFS_SB(sb);
if (tcon->dfs_path && cifs_sb->origin_fullpath &&
!strcasecmp(tcon->dfs_path, cifs_sb->origin_fullpath))
sd->sb = sb;
}
static inline struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon)
{
return __cifs_get_super(tcon_super_cb, tcon);
}
static inline void cifs_put_tcon_super(struct super_block *sb)
{
__cifs_put_super(sb);
}
#else
static inline struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon)
{
return ERR_PTR(-EOPNOTSUPP);
}
static inline void cifs_put_tcon_super(struct super_block *sb)
{
}
#endif
int update_super_prepath(struct cifs_tcon *tcon, char *prefix)
{
struct super_block *sb;
struct cifs_sb_info *cifs_sb;
int rc = 0;
sb = cifs_get_tcon_super(tcon);
if (IS_ERR(sb))
return PTR_ERR(sb);
cifs_sb = CIFS_SB(sb);
kfree(cifs_sb->prepath); kfree(cifs_sb->prepath);
if (prefix && *prefix) { if (prefix && *prefix) {
cifs_sb->prepath = kstrdup(prefix, GFP_ATOMIC); cifs_sb->prepath = kstrdup(prefix, GFP_ATOMIC);
if (!cifs_sb->prepath) { if (!cifs_sb->prepath)
rc = -ENOMEM; return -ENOMEM;
goto out;
}
convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb)); convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb));
} else } else
cifs_sb->prepath = NULL; cifs_sb->prepath = NULL;
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH; cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
return 0;
out:
cifs_put_tcon_super(sb);
return rc;
} }
#endif
...@@ -2844,6 +2844,7 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, ...@@ -2844,6 +2844,7 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses,
struct fsctl_get_dfs_referral_req *dfs_req = NULL; struct fsctl_get_dfs_referral_req *dfs_req = NULL;
struct get_dfs_referral_rsp *dfs_rsp = NULL; struct get_dfs_referral_rsp *dfs_rsp = NULL;
u32 dfs_req_size = 0, dfs_rsp_size = 0; u32 dfs_req_size = 0, dfs_rsp_size = 0;
int retry_count = 0;
cifs_dbg(FYI, "%s: path: %s\n", __func__, search_name); cifs_dbg(FYI, "%s: path: %s\n", __func__, search_name);
...@@ -2895,11 +2896,14 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, ...@@ -2895,11 +2896,14 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses,
true /* is_fsctl */, true /* is_fsctl */,
(char *)dfs_req, dfs_req_size, CIFSMaxBufSize, (char *)dfs_req, dfs_req_size, CIFSMaxBufSize,
(char **)&dfs_rsp, &dfs_rsp_size); (char **)&dfs_rsp, &dfs_rsp_size);
} while (rc == -EAGAIN); if (!is_retryable_error(rc))
break;
usleep_range(512, 2048);
} while (++retry_count < 5);
if (rc) { if (rc) {
if ((rc != -ENOENT) && (rc != -EOPNOTSUPP)) if (!is_retryable_error(rc) && rc != -ENOENT && rc != -EOPNOTSUPP)
cifs_tcon_dbg(VFS, "ioctl error in %s rc=%d\n", __func__, rc); cifs_tcon_dbg(VFS, "%s: ioctl error: rc=%d\n", __func__, rc);
goto out; goto out;
} }
......
...@@ -155,7 +155,11 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, ...@@ -155,7 +155,11 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
if (tcon == NULL) if (tcon == NULL)
return 0; return 0;
if (smb2_command == SMB2_TREE_CONNECT) /*
* Need to also skip SMB2_IOCTL because it is used for checking nested dfs links in
* cifs_tree_connect().
*/
if (smb2_command == SMB2_TREE_CONNECT || smb2_command == SMB2_IOCTL)
return 0; return 0;
if (tcon->tidStatus == CifsExiting) { if (tcon->tidStatus == CifsExiting) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment