Commit 5072010c authored by Paulo Alcantara (SUSE)'s avatar Paulo Alcantara (SUSE) Committed by Steve French

cifs: Fix DFS cache refresher for DFS links

As per MS-DFSC, when a DFS cache entry is expired and it is a DFS
link, then a new DFS referral must be sent to root server in order to
refresh the expired entry.

This patch ensures that all new DFS referrals for refreshing the cache
are sent to DFS root.
Signed-off-by: default avatarPaulo Alcantara (SUSE) <paulo@paulo.ac>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent f5307104
......@@ -526,12 +526,21 @@ extern int E_md4hash(const unsigned char *passwd, unsigned char *p16,
const struct nls_table *codepage);
extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8,
unsigned char *p24);
extern int
cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data,
const char *devname, bool is_smb3);
extern void
cifs_cleanup_volume_info_contents(struct smb_vol *volume_info);
extern struct TCP_Server_Info *
cifs_find_tcp_session(struct smb_vol *vol);
extern void cifs_put_smb_ses(struct cifs_ses *ses);
extern struct cifs_ses *
cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info);
void cifs_readdata_release(struct kref *refcount);
int cifs_async_readv(struct cifs_readdata *rdata);
int cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid);
......
......@@ -323,8 +323,6 @@ static int ip_connect(struct TCP_Server_Info *server);
static int generic_ip_connect(struct TCP_Server_Info *server);
static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);
static void cifs_prune_tlinks(struct work_struct *work);
static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data,
const char *devname, bool is_smb3);
static char *extract_hostname(const char *unc);
/*
......@@ -2904,8 +2902,7 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol)
return NULL;
}
static void
cifs_put_smb_ses(struct cifs_ses *ses)
void cifs_put_smb_ses(struct cifs_ses *ses)
{
unsigned int rc, xid;
struct TCP_Server_Info *server = ses->server;
......@@ -3082,7 +3079,7 @@ cifs_set_cifscreds(struct smb_vol *vol __attribute__((unused)),
* already got a server reference (server refcount +1). See
* cifs_get_tcon() for refcount explanations.
*/
static struct cifs_ses *
struct cifs_ses *
cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
{
int rc = -ENOMEM;
......@@ -4389,7 +4386,7 @@ static int mount_do_dfs_failover(const char *path,
}
#endif
static int
int
cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data,
const char *devname, bool is_smb3)
{
......@@ -4543,7 +4540,7 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
struct cifs_tcon *tcon = NULL;
struct TCP_Server_Info *server;
char *root_path = NULL, *full_path = NULL;
char *old_mountdata;
char *old_mountdata, *origin_mountdata = NULL;
int count;
rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
......@@ -4602,6 +4599,14 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
goto error;
}
/* Save DFS root volume information for DFS refresh worker */
origin_mountdata = kstrndup(cifs_sb->mountdata,
strlen(cifs_sb->mountdata), GFP_KERNEL);
if (!origin_mountdata) {
rc = -ENOMEM;
goto error;
}
if (cifs_sb->mountdata != old_mountdata) {
/* If we were redirected, reconnect to new target server */
mount_put_conns(cifs_sb, xid, server, ses, tcon);
......@@ -4710,7 +4715,7 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
}
spin_unlock(&cifs_tcp_ses_lock);
rc = dfs_cache_add_vol(vol, cifs_sb->origin_fullpath);
rc = dfs_cache_add_vol(origin_mountdata, vol, cifs_sb->origin_fullpath);
if (rc) {
kfree(cifs_sb->origin_fullpath);
goto error;
......@@ -4728,6 +4733,7 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
error:
kfree(full_path);
kfree(root_path);
kfree(origin_mountdata);
mount_put_conns(cifs_sb, xid, server, ses, tcon);
return rc;
}
......
......@@ -2,7 +2,7 @@
/*
* DFS referral cache routines
*
* Copyright (c) 2018 Paulo Alcantara <palcantara@suse.de>
* Copyright (c) 2018-2019 Paulo Alcantara <palcantara@suse.de>
*/
#include <linux/rcupdate.h>
......@@ -52,6 +52,7 @@ static struct kmem_cache *dfs_cache_slab __read_mostly;
struct dfs_cache_vol_info {
char *vi_fullpath;
struct smb_vol vi_vol;
char *vi_mntdata;
struct list_head vi_list;
};
......@@ -529,6 +530,7 @@ static inline void free_vol(struct dfs_cache_vol_info *vi)
{
list_del(&vi->vi_list);
kfree(vi->vi_fullpath);
kfree(vi->vi_mntdata);
cifs_cleanup_volume_info_contents(&vi->vi_vol);
kfree(vi);
}
......@@ -1139,17 +1141,18 @@ static int dup_vol(struct smb_vol *vol, struct smb_vol *new)
* dfs_cache_add_vol - add a cifs volume during mount() that will be handled by
* DFS cache refresh worker.
*
* @mntdata: mount data.
* @vol: cifs volume.
* @fullpath: origin full path.
*
* Return zero if volume was set up correctly, otherwise non-zero.
*/
int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath)
int dfs_cache_add_vol(char *mntdata, struct smb_vol *vol, const char *fullpath)
{
int rc;
struct dfs_cache_vol_info *vi;
if (!vol || !fullpath)
if (!vol || !fullpath || !mntdata)
return -EINVAL;
cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath);
......@@ -1168,6 +1171,8 @@ int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath)
if (rc)
goto err_free_fullpath;
vi->vi_mntdata = mntdata;
mutex_lock(&dfs_cache.dc_lock);
list_add_tail(&vi->vi_list, &dfs_cache.dc_vol_list);
mutex_unlock(&dfs_cache.dc_lock);
......@@ -1275,8 +1280,102 @@ static void get_tcons(struct TCP_Server_Info *server, struct list_head *head)
spin_unlock(&cifs_tcp_ses_lock);
}
static inline bool is_dfs_link(const char *path)
{
char *s;
s = strchr(path + 1, '\\');
if (!s)
return false;
return !!strchr(s + 1, '\\');
}
static inline char *get_dfs_root(const char *path)
{
char *s, *npath;
s = strchr(path + 1, '\\');
if (!s)
return ERR_PTR(-EINVAL);
s = strchr(s + 1, '\\');
if (!s)
return ERR_PTR(-EINVAL);
npath = kstrndup(path, s - path, GFP_KERNEL);
if (!npath)
return ERR_PTR(-ENOMEM);
return npath;
}
/* Find root SMB session out of a DFS link path */
static struct cifs_ses *find_root_ses(struct dfs_cache_vol_info *vi,
struct cifs_tcon *tcon, const char *path)
{
char *rpath;
int rc;
struct dfs_info3_param ref = {0};
char *mdata = NULL, *devname = NULL;
bool is_smb3 = tcon->ses->server->vals->header_preamble_size == 0;
struct TCP_Server_Info *server;
struct cifs_ses *ses;
struct smb_vol vol;
rpath = get_dfs_root(path);
if (IS_ERR(rpath))
return ERR_CAST(rpath);
memset(&vol, 0, sizeof(vol));
rc = dfs_cache_noreq_find(rpath, &ref, NULL);
if (rc) {
ses = ERR_PTR(rc);
goto out;
}
mdata = cifs_compose_mount_options(vi->vi_mntdata, rpath, &ref,
&devname);
free_dfs_info_param(&ref);
if (IS_ERR(mdata)) {
ses = ERR_CAST(mdata);
mdata = NULL;
goto out;
}
rc = cifs_setup_volume_info(&vol, mdata, devname, is_smb3);
kfree(devname);
if (rc) {
ses = ERR_PTR(rc);
goto out;
}
server = cifs_find_tcp_session(&vol);
if (IS_ERR_OR_NULL(server)) {
ses = ERR_PTR(-EHOSTDOWN);
goto out;
}
if (server->tcpStatus != CifsGood) {
cifs_put_tcp_session(server, 0);
ses = ERR_PTR(-EHOSTDOWN);
goto out;
}
ses = cifs_get_smb_ses(server, &vol);
out:
cifs_cleanup_volume_info_contents(&vol);
kfree(mdata);
kfree(rpath);
return ses;
}
/* Refresh DFS cache entry from a given tcon */
static void do_refresh_tcon(struct dfs_cache *dc, struct cifs_tcon *tcon)
static void do_refresh_tcon(struct dfs_cache *dc, struct dfs_cache_vol_info *vi,
struct cifs_tcon *tcon)
{
int rc = 0;
unsigned int xid;
......@@ -1285,6 +1384,7 @@ static void do_refresh_tcon(struct dfs_cache *dc, struct cifs_tcon *tcon)
struct dfs_cache_entry *ce;
struct dfs_info3_param *refs = NULL;
int numrefs = 0;
struct cifs_ses *root_ses = NULL, *ses;
xid = get_xid();
......@@ -1306,13 +1406,24 @@ static void do_refresh_tcon(struct dfs_cache *dc, struct cifs_tcon *tcon)
if (!cache_entry_expired(ce))
goto out;
if (unlikely(!tcon->ses->server->ops->get_dfs_refer)) {
/* If it's a DFS Link, then use root SMB session for refreshing it */
if (is_dfs_link(npath)) {
ses = root_ses = find_root_ses(vi, tcon, npath);
if (IS_ERR(ses)) {
rc = PTR_ERR(ses);
root_ses = NULL;
goto out;
}
} else {
ses = tcon->ses;
}
if (unlikely(!ses->server->ops->get_dfs_refer)) {
rc = -EOPNOTSUPP;
} else {
rc = tcon->ses->server->ops->get_dfs_refer(xid, tcon->ses, path,
&refs, &numrefs,
dc->dc_nlsc,
tcon->remap);
rc = ses->server->ops->get_dfs_refer(xid, ses, path, &refs,
&numrefs, dc->dc_nlsc,
tcon->remap);
if (!rc) {
mutex_lock(&dfs_cache_list_lock);
ce = __update_cache_entry(npath, refs, numrefs);
......@@ -1323,9 +1434,11 @@ static void do_refresh_tcon(struct dfs_cache *dc, struct cifs_tcon *tcon)
rc = PTR_ERR(ce);
}
}
if (rc)
cifs_dbg(FYI, "%s: failed to update expired entry\n", __func__);
out:
if (root_ses)
cifs_put_smb_ses(root_ses);
free_xid(xid);
free_normalized_path(path, npath);
}
......@@ -1333,9 +1446,6 @@ static void do_refresh_tcon(struct dfs_cache *dc, struct cifs_tcon *tcon)
/*
* Worker that will refresh DFS cache based on lowest TTL value from a DFS
* referral.
*
* FIXME: ensure that all requests are sent to DFS root for refreshing the
* cache.
*/
static void refresh_cache_worker(struct work_struct *work)
{
......@@ -1356,7 +1466,7 @@ static void refresh_cache_worker(struct work_struct *work)
goto next;
get_tcons(server, &list);
list_for_each_entry_safe(tcon, ntcon, &list, ulist) {
do_refresh_tcon(dc, tcon);
do_refresh_tcon(dc, vi, tcon);
list_del_init(&tcon->ulist);
cifs_put_tcon(tcon);
}
......
......@@ -2,7 +2,7 @@
/*
* DFS referral cache routines
*
* Copyright (c) 2018 Paulo Alcantara <palcantara@suse.de>
* Copyright (c) 2018-2019 Paulo Alcantara <palcantara@suse.de>
*/
#ifndef _CIFS_DFS_CACHE_H
......@@ -43,7 +43,8 @@ dfs_cache_noreq_update_tgthint(const char *path,
extern int dfs_cache_get_tgt_referral(const char *path,
const struct dfs_cache_tgt_iterator *it,
struct dfs_info3_param *ref);
extern int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath);
extern int dfs_cache_add_vol(char *mntdata, struct smb_vol *vol,
const char *fullpath);
extern int dfs_cache_update_vol(const char *fullpath,
struct TCP_Server_Info *server);
extern void dfs_cache_del_vol(const char *fullpath);
......
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