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

Commit 1bfe73c2 authored by Igor Mammedov's avatar Igor Mammedov Committed by Steve French
Browse files

Remote DFS root support.



Allows to mount share on a server that returns -EREMOTE
 at the tree connect stage or at the check on a full path
 accessibility.

Signed-off-by: default avatarIgor Mammedov <niallain@gmail.com>
Acked-by: default avatarJeff Layton <jlayton@redhat.com>
Signed-off-by: default avatarSteve French <sfrench@us.ibm.com>
parent 85a6dac5
Loading
Loading
Loading
Loading
+120 −32
Original line number Diff line number Diff line
@@ -2214,9 +2214,56 @@ is_path_accessible(int xid, struct cifsTconInfo *tcon,
	return rc;
}

static void
cleanup_volume_info(struct smb_vol **pvolume_info)
{
	struct smb_vol *volume_info;

	if (!pvolume_info && !*pvolume_info)
		return;

	volume_info = *pvolume_info;
	kzfree(volume_info->password);
	kfree(volume_info->UNC);
	kfree(volume_info->prepath);
	kfree(volume_info);
	*pvolume_info = NULL;
	return;
}

/* build_path_to_root returns full path to root when
 * we do not have an exiting connection (tcon) */
static char *
build_unc_path_to_root(const struct smb_vol *volume_info,
		const struct cifs_sb_info *cifs_sb)
{
	char *full_path;

	int unc_len = strnlen(volume_info->UNC, MAX_TREE_SIZE + 1);
	full_path = kmalloc(unc_len + cifs_sb->prepathlen + 1, GFP_KERNEL);
	if (full_path == NULL)
		return ERR_PTR(-ENOMEM);

	strncpy(full_path, volume_info->UNC, unc_len);
	if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) {
		int i;
		for (i = 0; i < unc_len; i++) {
			if (full_path[i] == '\\')
				full_path[i] = '/';
		}
	}

	if (cifs_sb->prepathlen)
		strncpy(full_path + unc_len, cifs_sb->prepath,
				cifs_sb->prepathlen);

	full_path[unc_len + cifs_sb->prepathlen] = 0; /* add trailing null */
	return full_path;
}

int
cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
	   char *mount_data, const char *devname)
		char *mount_data_global, const char *devname)
{
	int rc = 0;
	int xid;
@@ -2225,6 +2272,13 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
	struct cifsTconInfo *tcon = NULL;
	struct TCP_Server_Info *srvTcp = NULL;
	char   *full_path;
	struct dfs_info3_param *referrals = NULL;
	unsigned int num_referrals = 0;

	char *mount_data = mount_data_global;

try_mount_again:
	full_path = NULL;

	xid = GetXid();

@@ -2371,11 +2425,9 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
				}
			}

			/* check for null share name ie connect to dfs root */
			if ((strchr(volume_info->UNC + 3, '\\') == NULL)
			    && (strchr(volume_info->UNC + 3, '/') == NULL)) {
				/* rc = connect_to_dfs_path(...) */
				cFYI(1, ("DFS root not supported"));
				cERROR(1, ("Missing share name"));
				rc = -ENODEV;
				goto mount_fail_check;
			} else {
@@ -2392,7 +2444,7 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
				}
			}
			if (rc)
				goto mount_fail_check;
				goto remote_path_check;
			tcon->seal = volume_info->seal;
			write_lock(&cifs_tcp_ses_lock);
			list_add(&tcon->tcon_list, &pSesInfo->tcon_list);
@@ -2417,19 +2469,9 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
	/* BB FIXME fix time_gran to be larger for LANMAN sessions */
	sb->s_time_gran = 100;

mount_fail_check:
	/* on error free sesinfo and tcon struct if needed */
	if (rc) {
		/* If find_unc succeeded then rc == 0 so we can not end */
		/* up accidently freeing someone elses tcon struct */
		if (tcon)
			cifs_put_tcon(tcon);
		else if (pSesInfo)
			cifs_put_smb_ses(pSesInfo);
		else
			cifs_put_tcp_session(srvTcp);
		goto out;
	}
	if (rc)
		goto remote_path_check;

	cifs_sb->tcon = tcon;

	/* do not care if following two calls succeed - informational */
@@ -2461,7 +2503,9 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
		cifs_sb->rsize = min(cifs_sb->rsize,
			       (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE));

	if (!rc && cifs_sb->prepathlen) {
remote_path_check:
	/* check if a whole path (including prepath) is not remote */
	if (!rc && cifs_sb->prepathlen && tcon) {
		/* build_path_to_root works only when we have a valid tcon */
		full_path = cifs_build_path_to_root(cifs_sb);
		if (full_path == NULL) {
@@ -2469,13 +2513,66 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
			goto mount_fail_check;
		}
		rc = is_path_accessible(xid, tcon, cifs_sb, full_path);
		if (rc) {
			cERROR(1, ("Path %s in not accessible: %d",
						full_path, rc));
		if (rc != -EREMOTE) {
			kfree(full_path);
			goto mount_fail_check;
		}
		kfree(full_path);
	}

	/* get referral if needed */
	if (rc == -EREMOTE) {
		/* convert forward to back slashes in prepath here if needed */
		if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0)
			convert_delimiter(cifs_sb->prepath,
					CIFS_DIR_SEP(cifs_sb));
		full_path = build_unc_path_to_root(volume_info, cifs_sb);
		if (IS_ERR(full_path)) {
			rc = PTR_ERR(full_path);
			goto mount_fail_check;
		}

		cFYI(1, ("Getting referral for: %s", full_path));
		rc = get_dfs_path(xid, pSesInfo , full_path + 1,
			cifs_sb->local_nls, &num_referrals, &referrals,
			cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
		if (!rc && num_referrals > 0) {
			char *fake_devname = NULL;

			if (mount_data != mount_data_global)
				kfree(mount_data);
			mount_data = cifs_compose_mount_options(
					cifs_sb->mountdata, full_path + 1,
					referrals, &fake_devname);
			kfree(fake_devname);
			free_dfs_info_array(referrals, num_referrals);

			if (tcon)
				cifs_put_tcon(tcon);
			else if (pSesInfo)
				cifs_put_smb_ses(pSesInfo);

			cleanup_volume_info(&volume_info);
			FreeXid(xid);
			kfree(full_path);
			goto try_mount_again;
		}
	}

mount_fail_check:
	/* on error free sesinfo and tcon struct if needed */
	if (rc) {
		if (mount_data != mount_data_global)
			kfree(mount_data);
		/* If find_unc succeeded then rc == 0 so we can not end */
		/* up accidently freeing someone elses tcon struct */
		if (tcon)
			cifs_put_tcon(tcon);
		else if (pSesInfo)
			cifs_put_smb_ses(pSesInfo);
		else
			cifs_put_tcp_session(srvTcp);
		goto out;
	}

	/* volume_info->password is freed above when existing session found
@@ -2484,16 +2581,7 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
	password will be freed at unmount time) */
out:
	/* zero out password before freeing */
	if (volume_info) {
		if (volume_info->password != NULL) {
			memset(volume_info->password, 0,
				strlen(volume_info->password));
			kfree(volume_info->password);
		}
		kfree(volume_info->UNC);
		kfree(volume_info->prepath);
		kfree(volume_info);
	}
	cleanup_volume_info(&volume_info);
	FreeXid(xid);
	return rc;
}