summaryrefslogtreecommitdiffstats
path: root/fs/cifs/dfs_cache.c
diff options
context:
space:
mode:
authorPaulo Alcantara (SUSE) <paulo@paulo.ac>2019-03-19 20:54:29 +0100
committerSteve French <stfrench@microsoft.com>2019-05-08 06:24:54 +0200
commit5072010ccf0592950f7cfae0eb3c4008e8bf36f7 (patch)
treecde07048b7fe4faa26308571ca842ea9a36eda7c /fs/cifs/dfs_cache.c
parentcifs: don't use __constant_cpu_to_le32() (diff)
downloadlinux-5072010ccf0592950f7cfae0eb3c4008e8bf36f7.tar.xz
linux-5072010ccf0592950f7cfae0eb3c4008e8bf36f7.zip
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: Paulo Alcantara (SUSE) <paulo@paulo.ac> Signed-off-by: Steve French <stfrench@microsoft.com>
Diffstat (limited to 'fs/cifs/dfs_cache.c')
-rw-r--r--fs/cifs/dfs_cache.c140
1 files changed, 125 insertions, 15 deletions
diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c
index 09b7d0d4f6e4..85dc89d3a203 100644
--- a/fs/cifs/dfs_cache.c
+++ b/fs/cifs/dfs_cache.c
@@ -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 @@ err_free_username:
* 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 @@ out:
/*
* 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);
}