Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4a367dc0 authored by Paulo Alcantara's avatar Paulo Alcantara Committed by Steve French
Browse files

cifs: Add support for failover in cifs_mount()



This patch adds support for failover when failing to connect in
cifs_mount().

Signed-off-by: default avatarPaulo Alcantara <palcantara@suse.de>
Reviewed-by: default avatarAurelien Aptel <aaptel@suse.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 5a650501
Loading
Loading
Loading
Loading
+15 −5
Original line number Diff line number Diff line
@@ -255,20 +255,30 @@ static struct vfsmount *cifs_dfs_do_refmount(struct dentry *mntpt,
{
	struct vfsmount *mnt;
	char *mountdata;
	char *devname = NULL;
	char *devname;

	/*
	 * Always pass down the DFS full path to smb3_do_mount() so we
	 * can use it later for failover.
	 */
	devname = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL);
	if (!devname)
		return ERR_PTR(-ENOMEM);

	convert_delimiter(devname, '/');

	/* strip first '\' from fullpath */
	mountdata = cifs_compose_mount_options(cifs_sb->mountdata,
			fullpath + 1, ref, &devname);

	if (IS_ERR(mountdata))
					       fullpath + 1, ref, NULL);
	if (IS_ERR(mountdata)) {
		kfree(devname);
		return (struct vfsmount *)mountdata;
	}

	mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata);
	kfree(mountdata);
	kfree(devname);
	return mnt;

}

static void dump_referral(const struct dfs_info3_param *ref)
+218 −10
Original line number Diff line number Diff line
@@ -3891,10 +3891,11 @@ static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
 */
static char *
build_unc_path_to_root(const struct smb_vol *vol,
		const struct cifs_sb_info *cifs_sb)
		       const struct cifs_sb_info *cifs_sb, bool useppath)
{
	char *full_path, *pos;
	unsigned int pplen = vol->prepath ? strlen(vol->prepath) + 1 : 0;
	unsigned int pplen = useppath && vol->prepath ?
		strlen(vol->prepath) + 1 : 0;
	unsigned int unc_len = strnlen(vol->UNC, MAX_TREE_SIZE + 1);

	full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);
@@ -3939,7 +3940,7 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
	if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
		return -EREMOTE;

	full_path = build_unc_path_to_root(volume_info, cifs_sb);
	full_path = build_unc_path_to_root(volume_info, cifs_sb, true);
	if (IS_ERR(full_path))
		return PTR_ERR(full_path);

@@ -3971,6 +3972,143 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
	kfree(full_path);
	return rc;
}

static inline int get_next_dfs_tgt(const char *path,
				   struct dfs_cache_tgt_list *tgt_list,
				   struct dfs_cache_tgt_iterator **tgt_it)
{
	if (!*tgt_it)
		*tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
	else
		*tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it);
	return !*tgt_it ? -EHOSTDOWN : 0;
}

static int update_vol_info(const struct dfs_cache_tgt_iterator *tgt_it,
			   struct smb_vol *fake_vol, struct smb_vol *vol)
{
	const char *tgt = dfs_cache_get_tgt_name(tgt_it);
	int len = strlen(tgt) + 2;
	char *new_unc;

	new_unc = kmalloc(len, GFP_KERNEL);
	if (!new_unc)
		return -ENOMEM;
	snprintf(new_unc, len, "\\%s", tgt);

	kfree(vol->UNC);
	vol->UNC = new_unc;

	if (fake_vol->prepath) {
		kfree(vol->prepath);
		vol->prepath = fake_vol->prepath;
		fake_vol->prepath = NULL;
	}
	memcpy(&vol->dstaddr, &fake_vol->dstaddr, sizeof(vol->dstaddr));

	return 0;
}

static int setup_dfs_tgt_conn(const char *path,
			      const struct dfs_cache_tgt_iterator *tgt_it,
			      struct cifs_sb_info *cifs_sb,
			      struct smb_vol *vol,
			      unsigned int *xid,
			      struct TCP_Server_Info **server,
			      struct cifs_ses **ses,
			      struct cifs_tcon **tcon)
{
	int rc;
	struct dfs_info3_param ref = {0};
	char *mdata = NULL, *fake_devname = NULL;
	struct smb_vol fake_vol = {0};

	cifs_dbg(FYI, "%s: dfs path: %s\n", __func__, path);

	rc = dfs_cache_get_tgt_referral(path, tgt_it, &ref);
	if (rc)
		return rc;

	mdata = cifs_compose_mount_options(cifs_sb->mountdata, path, &ref,
					   &fake_devname);
	free_dfs_info_param(&ref);

	if (IS_ERR(mdata)) {
		rc = PTR_ERR(mdata);
		mdata = NULL;
	} else {
		cifs_dbg(FYI, "%s: fake_devname: %s\n", __func__, fake_devname);
		rc = cifs_setup_volume_info(&fake_vol, mdata, fake_devname,
					    false);
	}
	kfree(mdata);
	kfree(fake_devname);

	if (!rc) {
		/*
		 * We use a 'fake_vol' here because we need pass it down to the
		 * mount_{get,put} functions to test connection against new DFS
		 * targets.
		 */
		mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon);
		rc = mount_get_conns(&fake_vol, cifs_sb, xid, server, ses,
				     tcon);
		if (!rc) {
			/*
			 * We were able to connect to new target server.
			 * Update current volume info with new target server.
			 */
			rc = update_vol_info(tgt_it, &fake_vol, vol);
		}
	}
	cifs_cleanup_volume_info_contents(&fake_vol);
	return rc;
}

static int mount_do_dfs_failover(const char *path,
				 struct cifs_sb_info *cifs_sb,
				 struct smb_vol *vol,
				 struct cifs_ses *root_ses,
				 unsigned int *xid,
				 struct TCP_Server_Info **server,
				 struct cifs_ses **ses,
				 struct cifs_tcon **tcon)
{
	int rc;
	struct dfs_cache_tgt_list tgt_list;
	struct dfs_cache_tgt_iterator *tgt_it = NULL;

	if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
		return -EOPNOTSUPP;

	rc = dfs_cache_noreq_find(path, NULL, &tgt_list);
	if (rc)
		return rc;

	for (;;) {
		/* Get next DFS target server - if any */
		rc = get_next_dfs_tgt(path, &tgt_list, &tgt_it);
		if (rc)
			break;
		/* Connect to next DFS target */
		rc = setup_dfs_tgt_conn(path, tgt_it, cifs_sb, vol, xid, server,
					ses, tcon);
		if (!rc || rc == -EACCES || rc == -EOPNOTSUPP)
			break;
	}
	if (!rc) {
		/*
		 * Update DFS target hint in DFS referral cache with the target
		 * server we successfully reconnected to.
		 */
		rc = dfs_cache_update_tgthint(*xid, root_ses ? root_ses : *ses,
					      cifs_sb->local_nls,
					      cifs_remap(cifs_sb), path,
					      tgt_it);
	}
	dfs_cache_free_tgts(&tgt_list);
	return rc;
}
#endif

static int
@@ -4123,22 +4261,47 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
	int rc = 0;
	unsigned int xid;
	struct cifs_ses *ses;
	struct cifs_tcon *root_tcon = NULL;
	struct cifs_tcon *tcon = NULL;
	struct TCP_Server_Info *server;
	char *root_path = NULL, *full_path = NULL;
	char *old_mountdata;
	int count;

	rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
	if (!rc && tcon) {
		/* If not a standalone DFS root, then check if path is remote */
		rc = dfs_cache_find(xid, ses, cifs_sb->local_nls,
				    cifs_remap(cifs_sb), vol->UNC + 1, NULL,
				    NULL);
		if (rc) {
			rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
			if (!rc)
				goto out;
			if (rc != -EREMOTE)
				goto error;
		}
	if ((rc == -EACCES) || (rc == -EOPNOTSUPP) || (ses == NULL) || (server == NULL))
	}
	/*
	 * If first DFS target server went offline and we failed to connect it,
	 * server and ses pointers are NULL at this point, though we still have
	 * chance to get a cached DFS referral in expand_dfs_referral() and
	 * retry next target available in it.
	 *
	 * If a NULL ses ptr is passed to dfs_cache_find(), a lookup will be
	 * performed against DFS path and *no* requests will be sent to server
	 * for any new DFS referrals. Hence it's safe to skip checking whether
	 * server or ses ptr is NULL.
	 */
	if (rc == -EACCES || rc == -EOPNOTSUPP)
		goto error;

	root_path = build_unc_path_to_root(vol, cifs_sb, false);
	if (IS_ERR(root_path)) {
		rc = PTR_ERR(root_path);
		root_path = NULL;
		goto error;
	}

	/*
	 * Perform an unconditional check for whether there are DFS
@@ -4163,8 +4326,36 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
	if (rc) {
		if (rc == -EACCES || rc == -EOPNOTSUPP)
			goto error;
		/* Perform DFS failover to any other DFS targets */
		rc = mount_do_dfs_failover(root_path + 1, cifs_sb, vol, NULL,
					   &xid, &server, &ses, &tcon);
		if (rc)
			goto error;
	}

	kfree(root_path);
	root_path = build_unc_path_to_root(vol, cifs_sb, false);
	if (IS_ERR(root_path)) {
		rc = PTR_ERR(root_path);
		root_path = NULL;
		goto error;
	}
	/* Cache out resolved root server */
	(void)dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
			     root_path + 1, NULL, NULL);
	/*
	 * Save root tcon for additional DFS requests to update or create a new
	 * DFS cache entry, or even perform DFS failover.
	 */
	spin_lock(&cifs_tcp_ses_lock);
	tcon->tc_count++;
	tcon->dfs_path = root_path;
	root_path = NULL;
	tcon->remap = cifs_remap(cifs_sb);
	spin_unlock(&cifs_tcp_ses_lock);

	root_tcon = tcon;

	for (count = 1; ;) {
		if (!rc && tcon) {
			rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
@@ -4182,8 +4373,16 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
			break;
		}

		kfree(full_path);
		full_path = build_unc_path_to_root(vol, cifs_sb, true);
		if (IS_ERR(full_path)) {
			rc = PTR_ERR(full_path);
			full_path = NULL;
			break;
		}

		old_mountdata = cifs_sb->mountdata;
		rc = expand_dfs_referral(xid, tcon->ses, vol, cifs_sb,
		rc = expand_dfs_referral(xid, root_tcon->ses, vol, cifs_sb,
					 true);
		if (rc)
			break;
@@ -4194,11 +4393,18 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
					     &tcon);
		}
		if (rc) {
			if (rc == -EACCES || rc == -EOPNOTSUPP)
				break;
			/* Perform DFS failover to any other DFS targets */
			rc = mount_do_dfs_failover(full_path + 1, cifs_sb, vol,
						   root_tcon->ses, &xid,
						   &server, &ses, &tcon);
			if (rc == -EACCES || rc == -EOPNOTSUPP || !server ||
			    !ses)
				goto error;
		}
	}
	cifs_put_tcon(root_tcon);

	if (rc)
		goto error;
@@ -4214,6 +4420,8 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
	return mount_setup_tlink(cifs_sb, ses, tcon);

error:
	kfree(full_path);
	kfree(root_path);
	mount_put_conns(cifs_sb, xid, server, ses, tcon);
	return rc;
}
+3 −0
Original line number Diff line number Diff line
@@ -146,6 +146,9 @@ tconInfoFree(struct cifs_tcon *buf_to_free)
	kfree(buf_to_free->nativeFileSystem);
	kzfree(buf_to_free->password);
	kfree(buf_to_free->crfid.fid);
#ifdef CONFIG_CIFS_DFS_UPCALL
	kfree(buf_to_free->dfs_path);
#endif
	kfree(buf_to_free);
}