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: Paulo Alcantara (SUSE) <pc@cjr.nz> Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
parent
71e6864eac
commit
c88f7dcd6d
@ -307,12 +307,8 @@ static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt,
|
||||
static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
|
||||
{
|
||||
struct cifs_sb_info *cifs_sb;
|
||||
struct cifs_ses *ses;
|
||||
struct cifs_tcon *tcon;
|
||||
void *page;
|
||||
char *full_path, *root_path;
|
||||
unsigned int xid;
|
||||
int rc;
|
||||
char *full_path;
|
||||
struct vfsmount *mnt;
|
||||
|
||||
cifs_dbg(FYI, "in %s\n", __func__);
|
||||
@ -324,8 +320,6 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
|
||||
* the double backslashes usually used in the UNC. This function
|
||||
* gives us the latter, so we must adjust the result.
|
||||
*/
|
||||
mnt = ERR_PTR(-ENOMEM);
|
||||
|
||||
cifs_sb = CIFS_SB(mntpt->d_sb);
|
||||
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) {
|
||||
mnt = ERR_PTR(-EREMOTE);
|
||||
@ -341,60 +335,11 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
|
||||
}
|
||||
|
||||
convert_delimiter(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);
|
||||
cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__,
|
||||
full_path + 1, mnt);
|
||||
cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, full_path + 1, mnt);
|
||||
|
||||
free_root_path:
|
||||
kfree(root_path);
|
||||
free_full_path:
|
||||
free_dentry_path(page);
|
||||
cdda_exit:
|
||||
|
@ -61,11 +61,6 @@ struct cifs_sb_info {
|
||||
/* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */
|
||||
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 */
|
||||
uuid_t dfs_mount_id;
|
||||
/*
|
||||
|
@ -697,6 +697,19 @@ struct TCP_Server_Info {
|
||||
#endif
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
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
|
||||
};
|
||||
|
||||
@ -1097,7 +1110,6 @@ struct cifs_tcon {
|
||||
struct cached_fid crfid; /* Cached root fid */
|
||||
/* BB add field for back pointer to sb struct(s)? */
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
char *dfs_path; /* canonical DFS path */
|
||||
struct list_head ulist; /* cache update list */
|
||||
#endif
|
||||
};
|
||||
@ -1948,4 +1960,14 @@ static inline bool is_tcon_dfs(struct cifs_tcon *tcon)
|
||||
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 */
|
||||
|
@ -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);
|
||||
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_sharename(const char *unc);
|
||||
|
||||
@ -634,4 +634,7 @@ static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int 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 */
|
||||
|
1186
fs/cifs/connect.c
1186
fs/cifs/connect.c
File diff suppressed because it is too large
Load Diff
@ -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 */
|
||||
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 cache_entry *ce;
|
||||
struct dfs_info3_param *refs = NULL;
|
||||
@ -1422,6 +1422,20 @@ out:
|
||||
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
|
||||
*
|
||||
@ -1435,6 +1449,7 @@ out:
|
||||
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
|
||||
{
|
||||
struct cifs_tcon *tcon;
|
||||
struct TCP_Server_Info *server;
|
||||
struct mount_group *mg;
|
||||
struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
|
||||
int rc;
|
||||
@ -1443,13 +1458,15 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
|
||||
return -EINVAL;
|
||||
|
||||
tcon = cifs_sb_master_tcon(cifs_sb);
|
||||
if (!tcon->dfs_path) {
|
||||
cifs_dbg(FYI, "%s: not a dfs tcon\n", __func__);
|
||||
server = tcon->ses->server;
|
||||
|
||||
if (!server->origin_fullpath) {
|
||||
cifs_dbg(FYI, "%s: not a dfs mount\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
if (IS_ERR(mg)) {
|
||||
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);
|
||||
}
|
||||
kref_get(&mg->refcount);
|
||||
@ -1498,9 +1515,12 @@ static void refresh_mounts(struct cifs_ses **sessions)
|
||||
|
||||
spin_lock(&cifs_tcp_ses_lock);
|
||||
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(tcon, &ses->tcon_list, tcon_list) {
|
||||
if (tcon->dfs_path) {
|
||||
if (!tcon->ipc && !tcon->need_reconnect) {
|
||||
tcon->tc_count++;
|
||||
list_add_tail(&tcon->ulist, &tcons);
|
||||
}
|
||||
@ -1510,8 +1530,16 @@ static void refresh_mounts(struct cifs_ses **sessions)
|
||||
spin_unlock(&cifs_tcp_ses_lock);
|
||||
|
||||
list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
|
||||
struct TCP_Server_Info *server = tcon->ses->server;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -139,9 +139,6 @@ tconInfoFree(struct cifs_tcon *buf_to_free)
|
||||
kfree(buf_to_free->nativeFileSystem);
|
||||
kfree_sensitive(buf_to_free->password);
|
||||
kfree(buf_to_free->crfid.fid);
|
||||
#ifdef CONFIG_CIFS_DFS_UPCALL
|
||||
kfree(buf_to_free->dfs_path);
|
||||
#endif
|
||||
kfree(buf_to_free);
|
||||
}
|
||||
|
||||
@ -1288,69 +1285,20 @@ out:
|
||||
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);
|
||||
|
||||
if (prefix && *prefix) {
|
||||
cifs_sb->prepath = kstrdup(prefix, GFP_ATOMIC);
|
||||
if (!cifs_sb->prepath) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
if (!cifs_sb->prepath)
|
||||
return -ENOMEM;
|
||||
|
||||
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_tcon_super(sb);
|
||||
return rc;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
@ -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 get_dfs_referral_rsp *dfs_rsp = NULL;
|
||||
u32 dfs_req_size = 0, dfs_rsp_size = 0;
|
||||
int retry_count = 0;
|
||||
|
||||
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,
|
||||
true /* is_fsctl */,
|
||||
(char *)dfs_req, dfs_req_size, CIFSMaxBufSize,
|
||||
(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 != -ENOENT) && (rc != -EOPNOTSUPP))
|
||||
cifs_tcon_dbg(VFS, "ioctl error in %s rc=%d\n", __func__, rc);
|
||||
if (!is_retryable_error(rc) && rc != -ENOENT && rc != -EOPNOTSUPP)
|
||||
cifs_tcon_dbg(VFS, "%s: ioctl error: rc=%d\n", __func__, rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,11 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
|
||||
if (tcon == NULL)
|
||||
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;
|
||||
|
||||
if (tcon->tidStatus == CifsExiting) {
|
||||
|
Loading…
Reference in New Issue
Block a user