From: Sachin Prabhu <sprabhu@redhat.com>
Subject: cifs_get_root shouldn't use path with tree name
Patch-mainline: not yet, linux-cifs 2016-09-07
References: bsc#963655, bsc#979681

When a server returns the optional flag SMB_SHARE_IS_IN_DFS in response
to a tree connect, cifs_build_path_to_root() will return a pathname
which includes the tree name. This causes problems with cifs_get_root()
which separates each component and does a lookup for each component of
the path.

We encountered a problem with dfs shares hosted by a Netapp. when
connecting to nodes pointed to by the DFS share. The tree connect for
these nodes return SMB_SHARE_IS_IN_DFS even after the DFS node is
resolved and the node returned doesn't contain a DFS share. Other
servers(tested with MS Win2012 R2) do not and hence do not face this
problem. A workaround for Netapp servers is to mount with the
nodfs flag.
The return flag results in cifs_build_path_to_root() using the tree name
in building the path which is then split into its components and
individual lookup done for each component in cifs_get_root(). The first
component of the path in this case is the hostname which results in
object not found errors.

We have tested this patch both on our internal test servers as well as
the user's own servers and can confirm that it fixes the problem.

Signed-off-by: Sachin Prabhu <sprabhu@redhat.com>
Reported-by: Pierguido Lambri <plambri@redhat.com>

Acked-by: Aurélien Aptel <aaptel@suse.com>

--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -560,7 +560,7 @@ cifs_get_root(struct smb_vol *vol, struc
 		return dget(sb->s_root);
 
 	full_path = cifs_build_path_to_root(vol, cifs_sb,
-					    cifs_sb_master_tcon(cifs_sb));
+				cifs_sb_master_tcon(cifs_sb), 0);
 	if (full_path == NULL)
 		return ERR_PTR(-ENOMEM);
 
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -59,7 +59,8 @@ extern void cifs_destroy_idmaptrees(void
 extern char *build_path_from_dentry(struct dentry *);
 extern char *cifs_build_path_to_root(struct smb_vol *vol,
 				     struct cifs_sb_info *cifs_sb,
-				     struct cifs_tcon *tcon);
+				     struct cifs_tcon *tcon,
+				     int add_treename);
 extern char *build_wildcard_path_from_dentry(struct dentry *direntry);
 extern char *cifs_compose_mount_options(const char *sb_mountdata,
 		const char *fullpath, const struct dfs_info3_param *ref,
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -3198,7 +3198,8 @@ remote_path_check:
 	/* check if a whole path is not remote */
 	if (!rc && tcon) {
 		/* build_path_to_root works only when we have a valid tcon */
-		full_path = cifs_build_path_to_root(volume_info, cifs_sb, tcon);
+		full_path = cifs_build_path_to_root(volume_info, cifs_sb, tcon,
+					tcon->Flags & SMB_SHARE_IS_IN_DFS);
 		if (full_path == NULL) {
 			rc = -ENOMEM;
 			goto mount_fail_check;
--- a/fs/cifs/inode.c
+++ b/fs/cifs/inode.c
@@ -790,7 +790,7 @@ static const struct inode_operations cif
 };
 
 char *cifs_build_path_to_root(struct smb_vol *vol, struct cifs_sb_info *cifs_sb,
-			      struct cifs_tcon *tcon)
+			      struct cifs_tcon *tcon, int add_treename)
 {
 	int pplen = vol->prepath ? strlen(vol->prepath) : 0;
 	int dfsplen;
@@ -804,7 +804,7 @@ char *cifs_build_path_to_root(struct smb
 		return full_path;
 	}
 
-	if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
+	if (add_treename)
 		dfsplen = strnlen(tcon->treeName, MAX_TREE_SIZE + 1);
 	else
 		dfsplen = 0;
