diff options
Diffstat (limited to 'fs/smb/client/dfs.c')
| -rw-r--r-- | fs/smb/client/dfs.c | 271 | 
1 files changed, 150 insertions, 121 deletions
diff --git a/fs/smb/client/dfs.c b/fs/smb/client/dfs.c index ee772c3d9f00..81b84151450d 100644 --- a/fs/smb/client/dfs.c +++ b/fs/smb/client/dfs.c @@ -3,7 +3,6 @@   * Copyright (c) 2022 Paulo Alcantara <[email protected]>   */ -#include <linux/namei.h>  #include "cifsproto.h"  #include "cifs_debug.h"  #include "dns_resolve.h" @@ -96,51 +95,134 @@ static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)  	return 0;  } -static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path, -			const struct dfs_cache_tgt_iterator *tit) +static inline int parse_dfs_target(struct smb3_fs_context *ctx, +				   struct dfs_ref_walk *rw, +				   struct dfs_info3_param *tgt) +{ +	int rc; +	const char *fpath = ref_walk_fpath(rw) + 1; + +	rc = ref_walk_get_tgt(rw, tgt); +	if (!rc) +		rc = dfs_parse_target_referral(fpath, tgt, ctx); +	return rc; +} + +static int set_ref_paths(struct cifs_mount_ctx *mnt_ctx, +			 struct dfs_info3_param *tgt, +			 struct dfs_ref_walk *rw)  {  	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; -	struct dfs_info3_param ref = {}; -	bool is_refsrv; -	int rc, rc2; +	struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb; +	char *ref_path, *full_path; +	int rc; -	rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref); -	if (rc) +	full_path = smb3_fs_context_fullpath(ctx, CIFS_DIR_SEP(cifs_sb)); +	if (IS_ERR(full_path)) +		return PTR_ERR(full_path); + +	if (!tgt || (tgt->server_type == DFS_TYPE_LINK && +		     DFS_INTERLINK(tgt->flags))) +		ref_path = dfs_get_path(cifs_sb, ctx->UNC); +	else +		ref_path = dfs_get_path(cifs_sb, full_path); +	if (IS_ERR(ref_path)) { +		rc = PTR_ERR(ref_path); +		kfree(full_path);  		return rc; +	} +	ref_walk_path(rw) = ref_path; +	ref_walk_fpath(rw) = full_path; +	return 0; +} -	rc = dfs_parse_target_referral(full_path + 1, &ref, ctx); -	if (rc) -		goto out; +static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx, +			       struct dfs_ref_walk *rw) +{ +	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; +	struct dfs_info3_param tgt = {}; +	bool is_refsrv; +	int rc = -ENOENT; -	cifs_mount_put_conns(mnt_ctx); -	rc = get_session(mnt_ctx, ref_path); -	if (rc) -		goto out; +again: +	do { +		if (ref_walk_empty(rw)) { +			rc = dfs_get_referral(mnt_ctx, ref_walk_path(rw) + 1, +					      NULL, ref_walk_tl(rw)); +			if (rc) { +				rc = cifs_mount_get_tcon(mnt_ctx); +				if (!rc) +					rc = cifs_is_path_remote(mnt_ctx); +				continue; +			} +			if (!ref_walk_num_tgts(rw)) { +				rc = -ENOENT; +				continue; +			} +		} -	is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER); +		while (ref_walk_next_tgt(rw)) { +			rc = parse_dfs_target(ctx, rw, &tgt); +			if (rc) +				continue; -	rc = -EREMOTE; -	if (ref.flags & DFSREF_STORAGE_SERVER) { -		rc = cifs_mount_get_tcon(mnt_ctx); -		if (rc) -			goto out; +			cifs_mount_put_conns(mnt_ctx); +			rc = get_session(mnt_ctx, ref_walk_path(rw)); +			if (rc) +				continue; -		/* some servers may not advertise referral capability under ref.flags */ -		is_refsrv |= is_tcon_dfs(mnt_ctx->tcon); +			is_refsrv = tgt.server_type == DFS_TYPE_ROOT || +				DFS_INTERLINK(tgt.flags); +			ref_walk_set_tgt_hint(rw); -		rc = cifs_is_path_remote(mnt_ctx); -	} +			if (tgt.flags & DFSREF_STORAGE_SERVER) { +				rc = cifs_mount_get_tcon(mnt_ctx); +				if (!rc) +					rc = cifs_is_path_remote(mnt_ctx); +				if (!rc) +					break; +				if (rc != -EREMOTE) +					continue; +			} -	dfs_cache_noreq_update_tgthint(ref_path + 1, tit); +			if (is_refsrv) { +				rc = add_root_smb_session(mnt_ctx); +				if (rc) +					goto out; +			} -	if (rc == -EREMOTE && is_refsrv) { -		rc2 = add_root_smb_session(mnt_ctx); -		if (rc2) -			rc = rc2; -	} +			rc = ref_walk_advance(rw); +			if (!rc) { +				rc = set_ref_paths(mnt_ctx, &tgt, rw); +				if (!rc) { +					rc = -EREMOTE; +					goto again; +				} +			} +			if (rc != -ELOOP) +				goto out; +		} +	} while (rc && ref_walk_descend(rw));  out: -	free_dfs_info_param(&ref); +	free_dfs_info_param(&tgt); +	return rc; +} + +static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx) +{ +	struct dfs_ref_walk *rw; +	int rc; + +	rw = ref_walk_alloc(); +	if (IS_ERR(rw)) +		return PTR_ERR(rw); + +	ref_walk_init(rw); +	rc = set_ref_paths(mnt_ctx, NULL, rw); +	if (!rc) +		rc = __dfs_referral_walk(mnt_ctx, rw); +	ref_walk_free(rw);  	return rc;  } @@ -148,105 +230,48 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)  {  	struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;  	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; -	char *ref_path = NULL, *full_path = NULL; -	struct dfs_cache_tgt_iterator *tit;  	struct cifs_tcon *tcon; -	char *origin_fullpath = NULL; -	char sep = CIFS_DIR_SEP(cifs_sb); -	int num_links = 0; +	char *origin_fullpath;  	int rc; -	ref_path = dfs_get_path(cifs_sb, ctx->UNC); -	if (IS_ERR(ref_path)) -		return PTR_ERR(ref_path); +	origin_fullpath = dfs_get_path(cifs_sb, ctx->source); +	if (IS_ERR(origin_fullpath)) +		return PTR_ERR(origin_fullpath); -	full_path = smb3_fs_context_fullpath(ctx, sep); -	if (IS_ERR(full_path)) { -		rc = PTR_ERR(full_path); -		full_path = NULL; +	rc = dfs_referral_walk(mnt_ctx); +	if (rc)  		goto out; -	} -	origin_fullpath = kstrdup(full_path, GFP_KERNEL); -	if (!origin_fullpath) { -		rc = -ENOMEM; -		goto out; +	tcon = mnt_ctx->tcon; +	spin_lock(&tcon->tc_lock); +	if (!tcon->origin_fullpath) { +		tcon->origin_fullpath = origin_fullpath; +		origin_fullpath = NULL;  	} +	spin_unlock(&tcon->tc_lock); -	do { -		struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl); - -		rc = dfs_get_referral(mnt_ctx, ref_path + 1, NULL, &tl); -		if (rc) { -			rc = cifs_mount_get_tcon(mnt_ctx); -			if (!rc) -				rc = cifs_is_path_remote(mnt_ctx); -			break; -		} - -		tit = dfs_cache_get_tgt_iterator(&tl); -		if (!tit) { -			cifs_dbg(VFS, "%s: dfs referral (%s) with no targets\n", __func__, -				 ref_path + 1); -			rc = -ENOENT; -			dfs_cache_free_tgts(&tl); -			break; -		} - -		do { -			rc = get_dfs_conn(mnt_ctx, ref_path, full_path, tit); -			if (!rc) -				break; -			if (rc == -EREMOTE) { -				if (++num_links > MAX_NESTED_LINKS) { -					rc = -ELOOP; -					break; -				} -				kfree(ref_path); -				kfree(full_path); -				ref_path = full_path = NULL; - -				full_path = smb3_fs_context_fullpath(ctx, sep); -				if (IS_ERR(full_path)) { -					rc = PTR_ERR(full_path); -					full_path = NULL; -				} else { -					ref_path = dfs_get_path(cifs_sb, full_path); -					if (IS_ERR(ref_path)) { -						rc = PTR_ERR(ref_path); -						ref_path = NULL; -					} -				} -				break; -			} -		} while ((tit = dfs_cache_get_next_tgt(&tl, tit))); -		dfs_cache_free_tgts(&tl); -	} while (rc == -EREMOTE); - -	if (!rc) { -		tcon = mnt_ctx->tcon; - -		spin_lock(&tcon->tc_lock); -		if (!tcon->origin_fullpath) { -			tcon->origin_fullpath = origin_fullpath; -			origin_fullpath = NULL; -		} -		spin_unlock(&tcon->tc_lock); - -		if (list_empty(&tcon->dfs_ses_list)) { -			list_replace_init(&mnt_ctx->dfs_ses_list, -					  &tcon->dfs_ses_list); -			queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work, -					   dfs_cache_get_ttl() * HZ); -		} else { -			dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list); -		} +	if (list_empty(&tcon->dfs_ses_list)) { +		list_replace_init(&mnt_ctx->dfs_ses_list, &tcon->dfs_ses_list); +		queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work, +				   dfs_cache_get_ttl() * HZ); +	} else { +		dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);  	}  out:  	kfree(origin_fullpath); -	kfree(ref_path); -	kfree(full_path); +	return rc; +} + +/* Resolve UNC hostname in @ctx->source and set ip addr in @ctx->dstaddr */ +static int update_fs_context_dstaddr(struct smb3_fs_context *ctx) +{ +	struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr; +	int rc; + +	rc = dns_resolve_server_name_to_ip(ctx->source, addr, NULL); +	if (!rc) +		cifs_set_port(addr, ctx->port);  	return rc;  } @@ -256,6 +281,10 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)  	bool nodfs = ctx->nodfs;  	int rc; +	rc = update_fs_context_dstaddr(ctx); +	if (rc) +		return rc; +  	*isdfs = false;  	rc = get_session(mnt_ctx, NULL);  	if (rc) @@ -426,7 +455,7 @@ static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *t  	/* Try to tree connect to all dfs targets */  	for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {  		const char *target = dfs_cache_get_tgt_name(tit); -		struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl); +		DFS_CACHE_TGT_LIST(ntl);  		kfree(share);  		kfree(prefix); @@ -520,7 +549,7 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru  	int rc;  	struct TCP_Server_Info *server = tcon->ses->server;  	const struct smb_version_operations *ops = server->ops; -	struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl); +	DFS_CACHE_TGT_LIST(tl);  	struct cifs_sb_info *cifs_sb = NULL;  	struct super_block *sb = NULL;  	struct dfs_info3_param ref = {0};  |