cifs: Fix DFS cache refresher for DFS links
authorPaulo Alcantara (SUSE) <paulo@paulo.ac>
Tue, 19 Mar 2019 19:54:29 +0000 (16:54 -0300)
committerSteve French <stfrench@microsoft.com>
Wed, 8 May 2019 04:24:54 +0000 (23:24 -0500)
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>
fs/cifs/cifsproto.h
fs/cifs/connect.c
fs/cifs/dfs_cache.c
fs/cifs/dfs_cache.h

index 4f96b3b00a7a5437e2465b7dd8a33165720eb989..e23234207fc2cfbedaa7460eda6fabf0cd8e8210 100644 (file)
@@ -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);
index 4c0e44489f21497670131b5a889ae6f4eb0b843e..9de8f61088ac5bf2463cef23dde0543fade3b065 100644 (file)
@@ -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 @@ out:
 error:
        kfree(full_path);
        kfree(root_path);
+       kfree(origin_mountdata);
        mount_put_conns(cifs_sb, xid, server, ses, tcon);
        return rc;
 }
index 09b7d0d4f6e4fe5f6708d6387f6507ee20f6482f..85dc89d3a203946bde03da54b3a204c931cd88c4 100644 (file)
@@ -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);
                }
index 22f366514f3ad97ef6c9fbf9c189e6dbcc22b3e5..76c732943f5f82b979108a1ceae07105aaa22793 100644 (file)
@@ -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);