diff options
Diffstat (limited to 'fs/cifs/dfs_cache.c')
-rw-r--r-- | fs/cifs/dfs_cache.c | 140 |
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); } |