diff options
-rw-r--r-- | fs/cifs/cifsproto.h | 9 | ||||
-rw-r--r-- | fs/cifs/connect.c | 22 | ||||
-rw-r--r-- | fs/cifs/dfs_cache.c | 140 | ||||
-rw-r--r-- | fs/cifs/dfs_cache.h | 5 |
4 files changed, 151 insertions, 25 deletions
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index 4f96b3b00a7a..e23234207fc2 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -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); diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 4c0e44489f21..9de8f61088ac 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -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; } 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); } diff --git a/fs/cifs/dfs_cache.h b/fs/cifs/dfs_cache.h index 22f366514f3a..76c732943f5f 100644 --- a/fs/cifs/dfs_cache.h +++ b/fs/cifs/dfs_cache.h @@ -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); |