diff mbox

CIFS: Fix symbolic links usage

Message ID 1383314259-4694-1-git-send-email-piastryyy@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Pavel Shilovsky Nov. 1, 2013, 1:57 p.m. UTC
From: Pavel Shilovsky <piastry@etersoft.ru>

Now we treat any reparse point as a symbolic link and map it to a Unix
one that is not true in a common case due to many reparse point types
supported by SMB servers.

Distinguish reparse point types into two groups:
1) that can be accessed directly through a reparse point
(junctions, deduplicated files, NFS symlinks);
2) that need to be processed manually (Windows symbolic links, DFS);

and map only Windows symbolic links to Unix ones.

Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru>
---
 fs/cifs/cifsglob.h  |    2 +-
 fs/cifs/inode.c     |   23 +++++++++++++----------
 fs/cifs/readdir.c   |   40 ++++++++--------------------------------
 fs/cifs/smb1ops.c   |   21 ++++++++++++++++++++-
 fs/cifs/smb2inode.c |   16 ++++++++++++----
 fs/cifs/smb2proto.h |    2 +-
 6 files changed, 55 insertions(+), 49 deletions(-)

Comments

Shirish Pargaonkar Nov. 1, 2013, 3:08 p.m. UTC | #1
On Fri, Nov 1, 2013 at 8:57 AM, Pavel Shilovsky <piastryyy@gmail.com> wrote:
> From: Pavel Shilovsky <piastry@etersoft.ru>
>
> Now we treat any reparse point as a symbolic link and map it to a Unix
> one that is not true in a common case due to many reparse point types
> supported by SMB servers.
>
> Distinguish reparse point types into two groups:
> 1) that can be accessed directly through a reparse point
> (junctions, deduplicated files, NFS symlinks);
> 2) that need to be processed manually (Windows symbolic links, DFS);
>
> and map only Windows symbolic links to Unix ones.
>
> Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru>
> ---
>  fs/cifs/cifsglob.h  |    2 +-
>  fs/cifs/inode.c     |   23 +++++++++++++----------
>  fs/cifs/readdir.c   |   40 ++++++++--------------------------------
>  fs/cifs/smb1ops.c   |   21 ++++++++++++++++++++-
>  fs/cifs/smb2inode.c |   16 ++++++++++++----
>  fs/cifs/smb2proto.h |    2 +-
>  6 files changed, 55 insertions(+), 49 deletions(-)
>
> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> index a67cf12..b688f2b 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -261,7 +261,7 @@ struct smb_version_operations {
>         /* query path data from the server */
>         int (*query_path_info)(const unsigned int, struct cifs_tcon *,
>                                struct cifs_sb_info *, const char *,
> -                              FILE_ALL_INFO *, bool *);
> +                              FILE_ALL_INFO *, bool *, bool *);
>         /* query file data from the server */
>         int (*query_file_info)(const unsigned int, struct cifs_tcon *,
>                                struct cifs_fid *, FILE_ALL_INFO *);
> diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
> index 867b7cd..36f9ebb 100644
> --- a/fs/cifs/inode.c
> +++ b/fs/cifs/inode.c
> @@ -542,7 +542,8 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
>  /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
>  static void
>  cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
> -                      struct cifs_sb_info *cifs_sb, bool adjust_tz)
> +                      struct cifs_sb_info *cifs_sb, bool adjust_tz,
> +                      bool symlink)
>  {
>         struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
>
> @@ -569,7 +570,11 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>         fattr->cf_createtime = le64_to_cpu(info->CreationTime);
>
>         fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
> -       if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
> +
> +       if (symlink) {
> +               fattr->cf_mode = S_IFLNK;
> +               fattr->cf_dtype = DT_LNK;
> +       } else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>                 fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
>                 fattr->cf_dtype = DT_DIR;
>                 /*
> @@ -578,10 +583,6 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>                  */
>                 if (!tcon->unix_ext)
>                         fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
> -       } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> -               fattr->cf_mode = S_IFLNK;
> -               fattr->cf_dtype = DT_LNK;
> -               fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
>         } else {
>                 fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>                 fattr->cf_dtype = DT_REG;

Pavel, does this handle a case where it could be symbolic link
to a directory?

> @@ -626,7 +627,8 @@ cifs_get_file_info(struct file *filp)
>         rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data);
>         switch (rc) {
>         case 0:
> -               cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false);
> +               cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false,
> +                                      false);
>                 break;
>         case -EREMOTE:
>                 cifs_create_dfs_fattr(&fattr, inode->i_sb);
> @@ -673,6 +675,7 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
>         bool adjust_tz = false;
>         struct cifs_fattr fattr;
>         struct cifs_search_info *srchinf = NULL;
> +       bool symlink = false;
>
>         tlink = cifs_sb_tlink(cifs_sb);
>         if (IS_ERR(tlink))
> @@ -702,12 +705,12 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
>                 }
>                 data = (FILE_ALL_INFO *)buf;
>                 rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path,
> -                                                 data, &adjust_tz);
> +                                                 data, &adjust_tz, &symlink);
>         }
>
>         if (!rc) {
> -               cifs_all_info_to_fattr(&fattr, (FILE_ALL_INFO *)data, cifs_sb,
> -                                      adjust_tz);
> +               cifs_all_info_to_fattr(&fattr, data, cifs_sb, adjust_tz,
> +                                      symlink);
>         } else if (rc == -EREMOTE) {
>                 cifs_create_dfs_fattr(&fattr, sb);
>                 rc = 0;
> diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
> index 53a75f3..5940eca 100644
> --- a/fs/cifs/readdir.c
> +++ b/fs/cifs/readdir.c
> @@ -134,22 +134,6 @@ out:
>         dput(dentry);
>  }
>
> -/*
> - * Is it possible that this directory might turn out to be a DFS referral
> - * once we go to try and use it?
> - */
> -static bool
> -cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
> -{
> -#ifdef CONFIG_CIFS_DFS_UPCALL
> -       struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> -
> -       if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
> -               return true;
> -#endif
> -       return false;
> -}
> -
>  static void
>  cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>  {
> @@ -159,27 +143,19 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>         if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>                 fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
>                 fattr->cf_dtype = DT_DIR;
> -               /*
> -                * Windows CIFS servers generally make DFS referrals look
> -                * like directories in FIND_* responses with the reparse
> -                * attribute flag also set (since DFS junctions are
> -                * reparse points). We must revalidate at least these
> -                * directory inodes before trying to use them (if
> -                * they are DFS we will get PATH_NOT_COVERED back
> -                * when queried directly and can then try to connect
> -                * to the DFS target)
> -                */
> -               if (cifs_dfs_is_possible(cifs_sb) &&
> -                   (fattr->cf_cifsattrs & ATTR_REPARSE))
> -                       fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> -       } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> -               fattr->cf_mode = S_IFLNK;
> -               fattr->cf_dtype = DT_LNK;
>         } else {
>                 fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>                 fattr->cf_dtype = DT_REG;
>         }
>
> +       /*
> +        * We need to revalidate it further to make a decision about whether it
> +        * is a symbolic link, DFS referral or a reparse point with a direct
> +        * access like junctions, deduplicated files, NFS symlinks.
> +        */
> +       if (fattr->cf_cifsattrs & ATTR_REPARSE)
> +               fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> +
>         /* non-unix readdir doesn't provide nlink */
>         fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
>
> diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
> index ea99efe..47ab035 100644
> --- a/fs/cifs/smb1ops.c
> +++ b/fs/cifs/smb1ops.c
> @@ -534,10 +534,12 @@ cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
>  static int
>  cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>                      struct cifs_sb_info *cifs_sb, const char *full_path,
> -                    FILE_ALL_INFO *data, bool *adjustTZ)
> +                    FILE_ALL_INFO *data, bool *adjustTZ, bool *symlink)
>  {
>         int rc;
>
> +       *symlink = false;
> +
>         /* could do find first instead but this returns more info */
>         rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */,
>                               cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
> @@ -554,6 +556,23 @@ cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>                                                 CIFS_MOUNT_MAP_SPECIAL_CHR);
>                 *adjustTZ = true;
>         }
> +
> +       if (!rc && (le32_to_cpu(data->Attributes) & ATTR_REPARSE)) {
> +               int tmprc;
> +               int oplock = 0;
> +               __u16 netfid;
> +
> +               /* Need to check if this is a symbolic link or not */
> +               tmprc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
> +                                FILE_READ_ATTRIBUTES, 0, &netfid, &oplock,
> +                                NULL, cifs_sb->local_nls,
> +                       cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
> +               if (tmprc == -EOPNOTSUPP)
> +                       *symlink = true;
> +               else
> +                       CIFSSMBClose(xid, tcon, netfid);
> +       }
> +
>         return rc;
>  }
>
> diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c
> index 78ff88c..84c012a 100644
> --- a/fs/cifs/smb2inode.c
> +++ b/fs/cifs/smb2inode.c
> @@ -123,12 +123,13 @@ move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src)
>  int
>  smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>                      struct cifs_sb_info *cifs_sb, const char *full_path,
> -                    FILE_ALL_INFO *data, bool *adjust_tz)
> +                    FILE_ALL_INFO *data, bool *adjust_tz, bool *symlink)
>  {
>         int rc;
>         struct smb2_file_all_info *smb2_data;
>
>         *adjust_tz = false;
> +       *symlink = false;
>
>         smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + MAX_NAME * 2,
>                             GFP_KERNEL);
> @@ -136,9 +137,16 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>                 return -ENOMEM;
>
>         rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> -                               FILE_READ_ATTRIBUTES, FILE_OPEN,
> -                               OPEN_REPARSE_POINT, smb2_data,
> -                               SMB2_OP_QUERY_INFO);
> +                               FILE_READ_ATTRIBUTES, FILE_OPEN, 0,
> +                               smb2_data, SMB2_OP_QUERY_INFO);
> +       if (rc == -EOPNOTSUPP) {
> +               *symlink = true;
> +               /* Failed on a symbolic link - query a reparse point info */
> +               rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> +                                       FILE_READ_ATTRIBUTES, FILE_OPEN,
> +                                       OPEN_REPARSE_POINT, smb2_data,
> +                                       SMB2_OP_QUERY_INFO);
> +       }
>         if (rc)
>                 goto out;
>
> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
> index 313813e..b4eea10 100644
> --- a/fs/cifs/smb2proto.h
> +++ b/fs/cifs/smb2proto.h
> @@ -61,7 +61,7 @@ extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst,
>  extern int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>                                 struct cifs_sb_info *cifs_sb,
>                                 const char *full_path, FILE_ALL_INFO *data,
> -                               bool *adjust_tz);
> +                               bool *adjust_tz, bool *symlink);
>  extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
>                               const char *full_path, __u64 size,
>                               struct cifs_sb_info *cifs_sb, bool set_alloc);
> --
> 1.7.10.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jeff Layton Nov. 1, 2013, 5:42 p.m. UTC | #2
On Fri,  1 Nov 2013 17:57:39 +0400
Pavel Shilovsky <piastryyy@gmail.com> wrote:

> From: Pavel Shilovsky <piastry@etersoft.ru>
> 
> Now we treat any reparse point as a symbolic link and map it to a Unix
> one that is not true in a common case due to many reparse point types
> supported by SMB servers.
> 
> Distinguish reparse point types into two groups:
> 1) that can be accessed directly through a reparse point
> (junctions, deduplicated files, NFS symlinks);
> 2) that need to be processed manually (Windows symbolic links, DFS);
> 
> and map only Windows symbolic links to Unix ones.
> 
> Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru>
> ---
>  fs/cifs/cifsglob.h  |    2 +-
>  fs/cifs/inode.c     |   23 +++++++++++++----------
>  fs/cifs/readdir.c   |   40 ++++++++--------------------------------
>  fs/cifs/smb1ops.c   |   21 ++++++++++++++++++++-
>  fs/cifs/smb2inode.c |   16 ++++++++++++----
>  fs/cifs/smb2proto.h |    2 +-
>  6 files changed, 55 insertions(+), 49 deletions(-)
> 
> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> index a67cf12..b688f2b 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -261,7 +261,7 @@ struct smb_version_operations {
>  	/* query path data from the server */
>  	int (*query_path_info)(const unsigned int, struct cifs_tcon *,
>  			       struct cifs_sb_info *, const char *,
> -			       FILE_ALL_INFO *, bool *);
> +			       FILE_ALL_INFO *, bool *, bool *);


This looks much better for performance, but I think it really shines a
light on what an abortion the query_path_info and query_file_info
operations are. They work with a FILE_ALL_INFO struct which is SMB1
specific, so for SMB2/3 we have to convert data to something that
doesn't even match the protocol.

I think it would be best to base this fix on top of a patchset to fix
that properly. Those functions should be changed to pass data around in
a cifs_fattr. With that, you could just add a new cifs_fattr->flags
value and you wouldn't need to add this extra bool * argument.


>  	/* query file data from the server */
>  	int (*query_file_info)(const unsigned int, struct cifs_tcon *,
>  			       struct cifs_fid *, FILE_ALL_INFO *);
> diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
> index 867b7cd..36f9ebb 100644
> --- a/fs/cifs/inode.c
> +++ b/fs/cifs/inode.c
> @@ -542,7 +542,8 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
>  /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
>  static void
>  cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
> -		       struct cifs_sb_info *cifs_sb, bool adjust_tz)
> +		       struct cifs_sb_info *cifs_sb, bool adjust_tz,
> +		       bool symlink)
>  {
>  	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
>  
> @@ -569,7 +570,11 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>  	fattr->cf_createtime = le64_to_cpu(info->CreationTime);
>  
>  	fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
> -	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
> +
> +	if (symlink) {
> +		fattr->cf_mode = S_IFLNK;
> +		fattr->cf_dtype = DT_LNK;
> +	} else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>  		fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
>  		fattr->cf_dtype = DT_DIR;
>  		/*
> @@ -578,10 +583,6 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>  		 */
>  		if (!tcon->unix_ext)
>  			fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
> -	} else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> -		fattr->cf_mode = S_IFLNK;
> -		fattr->cf_dtype = DT_LNK;
> -		fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
>  	} else {
>  		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>  		fattr->cf_dtype = DT_REG;
> @@ -626,7 +627,8 @@ cifs_get_file_info(struct file *filp)
>  	rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data);
>  	switch (rc) {
>  	case 0:
> -		cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false);
> +		cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false,
> +				       false);
>  		break;
>  	case -EREMOTE:
>  		cifs_create_dfs_fattr(&fattr, inode->i_sb);
> @@ -673,6 +675,7 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
>  	bool adjust_tz = false;
>  	struct cifs_fattr fattr;
>  	struct cifs_search_info *srchinf = NULL;
> +	bool symlink = false;
>  
>  	tlink = cifs_sb_tlink(cifs_sb);
>  	if (IS_ERR(tlink))
> @@ -702,12 +705,12 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
>  		}
>  		data = (FILE_ALL_INFO *)buf;
>  		rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path,
> -						  data, &adjust_tz);
> +						  data, &adjust_tz, &symlink);
>  	}
>  
>  	if (!rc) {
> -		cifs_all_info_to_fattr(&fattr, (FILE_ALL_INFO *)data, cifs_sb,
> -				       adjust_tz);
> +		cifs_all_info_to_fattr(&fattr, data, cifs_sb, adjust_tz,
> +				       symlink);
>  	} else if (rc == -EREMOTE) {
>  		cifs_create_dfs_fattr(&fattr, sb);
>  		rc = 0;
> diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
> index 53a75f3..5940eca 100644
> --- a/fs/cifs/readdir.c
> +++ b/fs/cifs/readdir.c
> @@ -134,22 +134,6 @@ out:
>  	dput(dentry);
>  }
>  
> -/*
> - * Is it possible that this directory might turn out to be a DFS referral
> - * once we go to try and use it?
> - */
> -static bool
> -cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
> -{
> -#ifdef CONFIG_CIFS_DFS_UPCALL
> -	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> -
> -	if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
> -		return true;
> -#endif
> -	return false;
> -}
> -
>  static void
>  cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>  {
> @@ -159,27 +143,19 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>  	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>  		fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
>  		fattr->cf_dtype = DT_DIR;
> -		/*
> -		 * Windows CIFS servers generally make DFS referrals look
> -		 * like directories in FIND_* responses with the reparse
> -		 * attribute flag also set (since DFS junctions are
> -		 * reparse points). We must revalidate at least these
> -		 * directory inodes before trying to use them (if
> -		 * they are DFS we will get PATH_NOT_COVERED back
> -		 * when queried directly and can then try to connect
> -		 * to the DFS target)
> -		 */
> -		if (cifs_dfs_is_possible(cifs_sb) &&
> -		    (fattr->cf_cifsattrs & ATTR_REPARSE))
> -			fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> -	} else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> -		fattr->cf_mode = S_IFLNK;
> -		fattr->cf_dtype = DT_LNK;
>  	} else {
>  		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>  		fattr->cf_dtype = DT_REG;
>  	}
>  
> +	/*
> +	 * We need to revalidate it further to make a decision about whether it
> +	 * is a symbolic link, DFS referral or a reparse point with a direct
> +	 * access like junctions, deduplicated files, NFS symlinks.
> +	 */
> +	if (fattr->cf_cifsattrs & ATTR_REPARSE)
> +		fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> +
>  	/* non-unix readdir doesn't provide nlink */
>  	fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
>  
> diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
> index ea99efe..47ab035 100644
> --- a/fs/cifs/smb1ops.c
> +++ b/fs/cifs/smb1ops.c
> @@ -534,10 +534,12 @@ cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
>  static int
>  cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  		     struct cifs_sb_info *cifs_sb, const char *full_path,
> -		     FILE_ALL_INFO *data, bool *adjustTZ)
> +		     FILE_ALL_INFO *data, bool *adjustTZ, bool *symlink)
>  {
>  	int rc;
>  
> +	*symlink = false;
> +
>  	/* could do find first instead but this returns more info */
>  	rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */,
>  			      cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
> @@ -554,6 +556,23 @@ cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  						CIFS_MOUNT_MAP_SPECIAL_CHR);
>  		*adjustTZ = true;
>  	}
> +
> +	if (!rc && (le32_to_cpu(data->Attributes) & ATTR_REPARSE)) {
> +		int tmprc;
> +		int oplock = 0;
> +		__u16 netfid;
> +
> +		/* Need to check if this is a symbolic link or not */
> +		tmprc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
> +				 FILE_READ_ATTRIBUTES, 0, &netfid, &oplock,
> +				 NULL, cifs_sb->local_nls,
> +			cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
> +		if (tmprc == -EOPNOTSUPP)
> +			*symlink = true;
> +		else
> +			CIFSSMBClose(xid, tcon, netfid);
> +	}
> +
>  	return rc;
>  }
>  
> diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c
> index 78ff88c..84c012a 100644
> --- a/fs/cifs/smb2inode.c
> +++ b/fs/cifs/smb2inode.c
> @@ -123,12 +123,13 @@ move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src)
>  int
>  smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  		     struct cifs_sb_info *cifs_sb, const char *full_path,
> -		     FILE_ALL_INFO *data, bool *adjust_tz)
> +		     FILE_ALL_INFO *data, bool *adjust_tz, bool *symlink)
>  {
>  	int rc;
>  	struct smb2_file_all_info *smb2_data;
>  
>  	*adjust_tz = false;
> +	*symlink = false;
>  
>  	smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + MAX_NAME * 2,
>  			    GFP_KERNEL);
> @@ -136,9 +137,16 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  		return -ENOMEM;
>  
>  	rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> -				FILE_READ_ATTRIBUTES, FILE_OPEN,
> -				OPEN_REPARSE_POINT, smb2_data,
> -				SMB2_OP_QUERY_INFO);
> +				FILE_READ_ATTRIBUTES, FILE_OPEN, 0,
> +				smb2_data, SMB2_OP_QUERY_INFO);
> +	if (rc == -EOPNOTSUPP) {
> +		*symlink = true;
> +		/* Failed on a symbolic link - query a reparse point info */
> +		rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> +					FILE_READ_ATTRIBUTES, FILE_OPEN,
> +					OPEN_REPARSE_POINT, smb2_data,
> +					SMB2_OP_QUERY_INFO);
> +	}
>  	if (rc)
>  		goto out;
>  
> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
> index 313813e..b4eea10 100644
> --- a/fs/cifs/smb2proto.h
> +++ b/fs/cifs/smb2proto.h
> @@ -61,7 +61,7 @@ extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst,
>  extern int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  				struct cifs_sb_info *cifs_sb,
>  				const char *full_path, FILE_ALL_INFO *data,
> -				bool *adjust_tz);
> +				bool *adjust_tz, bool *symlink);
>  extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
>  			      const char *full_path, __u64 size,
>  			      struct cifs_sb_info *cifs_sb, bool set_alloc);
Steve French Nov. 1, 2013, 5:49 p.m. UTC | #3
FILE_ALL_INFO is not SMB1 specific, we request it in SMB2/SMB3 as well
and it is still a common level.  See section 2.2.37 of MS-SMB2 or FSCC
description at:


http://msdn.microsoft.com/en-us/library/cc232117.aspx

Is there a different level that ou would like to call?

On Fri, Nov 1, 2013 at 12:42 PM, Jeff Layton <jlayton@redhat.com> wrote:
> On Fri,  1 Nov 2013 17:57:39 +0400
> Pavel Shilovsky <piastryyy@gmail.com> wrote:
>
>> From: Pavel Shilovsky <piastry@etersoft.ru>
>>
>> Now we treat any reparse point as a symbolic link and map it to a Unix
>> one that is not true in a common case due to many reparse point types
>> supported by SMB servers.
>>
>> Distinguish reparse point types into two groups:
>> 1) that can be accessed directly through a reparse point
>> (junctions, deduplicated files, NFS symlinks);
>> 2) that need to be processed manually (Windows symbolic links, DFS);
>>
>> and map only Windows symbolic links to Unix ones.
>>
>> Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru>
>> ---
>>  fs/cifs/cifsglob.h  |    2 +-
>>  fs/cifs/inode.c     |   23 +++++++++++++----------
>>  fs/cifs/readdir.c   |   40 ++++++++--------------------------------
>>  fs/cifs/smb1ops.c   |   21 ++++++++++++++++++++-
>>  fs/cifs/smb2inode.c |   16 ++++++++++++----
>>  fs/cifs/smb2proto.h |    2 +-
>>  6 files changed, 55 insertions(+), 49 deletions(-)
>>
>> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
>> index a67cf12..b688f2b 100644
>> --- a/fs/cifs/cifsglob.h
>> +++ b/fs/cifs/cifsglob.h
>> @@ -261,7 +261,7 @@ struct smb_version_operations {
>>       /* query path data from the server */
>>       int (*query_path_info)(const unsigned int, struct cifs_tcon *,
>>                              struct cifs_sb_info *, const char *,
>> -                            FILE_ALL_INFO *, bool *);
>> +                            FILE_ALL_INFO *, bool *, bool *);
>
>
> This looks much better for performance, but I think it really shines a
> light on what an abortion the query_path_info and query_file_info
> operations are. They work with a FILE_ALL_INFO struct which is SMB1
> specific, so for SMB2/3 we have to convert data to something that
> doesn't even match the protocol.
>
> I think it would be best to base this fix on top of a patchset to fix
> that properly. Those functions should be changed to pass data around in
> a cifs_fattr. With that, you could just add a new cifs_fattr->flags
> value and you wouldn't need to add this extra bool * argument.
>
>
>>       /* query file data from the server */
>>       int (*query_file_info)(const unsigned int, struct cifs_tcon *,
>>                              struct cifs_fid *, FILE_ALL_INFO *);
>> diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
>> index 867b7cd..36f9ebb 100644
>> --- a/fs/cifs/inode.c
>> +++ b/fs/cifs/inode.c
>> @@ -542,7 +542,8 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
>>  /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
>>  static void
>>  cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>> -                    struct cifs_sb_info *cifs_sb, bool adjust_tz)
>> +                    struct cifs_sb_info *cifs_sb, bool adjust_tz,
>> +                    bool symlink)
>>  {
>>       struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
>>
>> @@ -569,7 +570,11 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>>       fattr->cf_createtime = le64_to_cpu(info->CreationTime);
>>
>>       fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
>> -     if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>> +
>> +     if (symlink) {
>> +             fattr->cf_mode = S_IFLNK;
>> +             fattr->cf_dtype = DT_LNK;
>> +     } else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>>               fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
>>               fattr->cf_dtype = DT_DIR;
>>               /*
>> @@ -578,10 +583,6 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>>                */
>>               if (!tcon->unix_ext)
>>                       fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
>> -     } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
>> -             fattr->cf_mode = S_IFLNK;
>> -             fattr->cf_dtype = DT_LNK;
>> -             fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
>>       } else {
>>               fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>>               fattr->cf_dtype = DT_REG;
>> @@ -626,7 +627,8 @@ cifs_get_file_info(struct file *filp)
>>       rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data);
>>       switch (rc) {
>>       case 0:
>> -             cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false);
>> +             cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false,
>> +                                    false);
>>               break;
>>       case -EREMOTE:
>>               cifs_create_dfs_fattr(&fattr, inode->i_sb);
>> @@ -673,6 +675,7 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
>>       bool adjust_tz = false;
>>       struct cifs_fattr fattr;
>>       struct cifs_search_info *srchinf = NULL;
>> +     bool symlink = false;
>>
>>       tlink = cifs_sb_tlink(cifs_sb);
>>       if (IS_ERR(tlink))
>> @@ -702,12 +705,12 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
>>               }
>>               data = (FILE_ALL_INFO *)buf;
>>               rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path,
>> -                                               data, &adjust_tz);
>> +                                               data, &adjust_tz, &symlink);
>>       }
>>
>>       if (!rc) {
>> -             cifs_all_info_to_fattr(&fattr, (FILE_ALL_INFO *)data, cifs_sb,
>> -                                    adjust_tz);
>> +             cifs_all_info_to_fattr(&fattr, data, cifs_sb, adjust_tz,
>> +                                    symlink);
>>       } else if (rc == -EREMOTE) {
>>               cifs_create_dfs_fattr(&fattr, sb);
>>               rc = 0;
>> diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
>> index 53a75f3..5940eca 100644
>> --- a/fs/cifs/readdir.c
>> +++ b/fs/cifs/readdir.c
>> @@ -134,22 +134,6 @@ out:
>>       dput(dentry);
>>  }
>>
>> -/*
>> - * Is it possible that this directory might turn out to be a DFS referral
>> - * once we go to try and use it?
>> - */
>> -static bool
>> -cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
>> -{
>> -#ifdef CONFIG_CIFS_DFS_UPCALL
>> -     struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
>> -
>> -     if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
>> -             return true;
>> -#endif
>> -     return false;
>> -}
>> -
>>  static void
>>  cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>>  {
>> @@ -159,27 +143,19 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>>       if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>>               fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
>>               fattr->cf_dtype = DT_DIR;
>> -             /*
>> -              * Windows CIFS servers generally make DFS referrals look
>> -              * like directories in FIND_* responses with the reparse
>> -              * attribute flag also set (since DFS junctions are
>> -              * reparse points). We must revalidate at least these
>> -              * directory inodes before trying to use them (if
>> -              * they are DFS we will get PATH_NOT_COVERED back
>> -              * when queried directly and can then try to connect
>> -              * to the DFS target)
>> -              */
>> -             if (cifs_dfs_is_possible(cifs_sb) &&
>> -                 (fattr->cf_cifsattrs & ATTR_REPARSE))
>> -                     fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
>> -     } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
>> -             fattr->cf_mode = S_IFLNK;
>> -             fattr->cf_dtype = DT_LNK;
>>       } else {
>>               fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>>               fattr->cf_dtype = DT_REG;
>>       }
>>
>> +     /*
>> +      * We need to revalidate it further to make a decision about whether it
>> +      * is a symbolic link, DFS referral or a reparse point with a direct
>> +      * access like junctions, deduplicated files, NFS symlinks.
>> +      */
>> +     if (fattr->cf_cifsattrs & ATTR_REPARSE)
>> +             fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
>> +
>>       /* non-unix readdir doesn't provide nlink */
>>       fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
>>
>> diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
>> index ea99efe..47ab035 100644
>> --- a/fs/cifs/smb1ops.c
>> +++ b/fs/cifs/smb1ops.c
>> @@ -534,10 +534,12 @@ cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
>>  static int
>>  cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>>                    struct cifs_sb_info *cifs_sb, const char *full_path,
>> -                  FILE_ALL_INFO *data, bool *adjustTZ)
>> +                  FILE_ALL_INFO *data, bool *adjustTZ, bool *symlink)
>>  {
>>       int rc;
>>
>> +     *symlink = false;
>> +
>>       /* could do find first instead but this returns more info */
>>       rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */,
>>                             cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
>> @@ -554,6 +556,23 @@ cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>>                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
>>               *adjustTZ = true;
>>       }
>> +
>> +     if (!rc && (le32_to_cpu(data->Attributes) & ATTR_REPARSE)) {
>> +             int tmprc;
>> +             int oplock = 0;
>> +             __u16 netfid;
>> +
>> +             /* Need to check if this is a symbolic link or not */
>> +             tmprc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
>> +                              FILE_READ_ATTRIBUTES, 0, &netfid, &oplock,
>> +                              NULL, cifs_sb->local_nls,
>> +                     cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
>> +             if (tmprc == -EOPNOTSUPP)
>> +                     *symlink = true;
>> +             else
>> +                     CIFSSMBClose(xid, tcon, netfid);
>> +     }
>> +
>>       return rc;
>>  }
>>
>> diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c
>> index 78ff88c..84c012a 100644
>> --- a/fs/cifs/smb2inode.c
>> +++ b/fs/cifs/smb2inode.c
>> @@ -123,12 +123,13 @@ move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src)
>>  int
>>  smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>>                    struct cifs_sb_info *cifs_sb, const char *full_path,
>> -                  FILE_ALL_INFO *data, bool *adjust_tz)
>> +                  FILE_ALL_INFO *data, bool *adjust_tz, bool *symlink)
>>  {
>>       int rc;
>>       struct smb2_file_all_info *smb2_data;
>>
>>       *adjust_tz = false;
>> +     *symlink = false;
>>
>>       smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + MAX_NAME * 2,
>>                           GFP_KERNEL);
>> @@ -136,9 +137,16 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>>               return -ENOMEM;
>>
>>       rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
>> -                             FILE_READ_ATTRIBUTES, FILE_OPEN,
>> -                             OPEN_REPARSE_POINT, smb2_data,
>> -                             SMB2_OP_QUERY_INFO);
>> +                             FILE_READ_ATTRIBUTES, FILE_OPEN, 0,
>> +                             smb2_data, SMB2_OP_QUERY_INFO);
>> +     if (rc == -EOPNOTSUPP) {
>> +             *symlink = true;
>> +             /* Failed on a symbolic link - query a reparse point info */
>> +             rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
>> +                                     FILE_READ_ATTRIBUTES, FILE_OPEN,
>> +                                     OPEN_REPARSE_POINT, smb2_data,
>> +                                     SMB2_OP_QUERY_INFO);
>> +     }
>>       if (rc)
>>               goto out;
>>
>> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
>> index 313813e..b4eea10 100644
>> --- a/fs/cifs/smb2proto.h
>> +++ b/fs/cifs/smb2proto.h
>> @@ -61,7 +61,7 @@ extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst,
>>  extern int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>>                               struct cifs_sb_info *cifs_sb,
>>                               const char *full_path, FILE_ALL_INFO *data,
>> -                             bool *adjust_tz);
>> +                             bool *adjust_tz, bool *symlink);
>>  extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
>>                             const char *full_path, __u64 size,
>>                             struct cifs_sb_info *cifs_sb, bool set_alloc);
>
>
> --
> Jeff Layton <jlayton@redhat.com>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jeff Layton Nov. 1, 2013, 6 p.m. UTC | #4
On Fri, 1 Nov 2013 12:49:47 -0500
Steve French <smfrench@gmail.com> wrote:

> FILE_ALL_INFO is not SMB1 specific, we request it in SMB2/SMB3 as well
> and it is still a common level.  See section 2.2.37 of MS-SMB2 or FSCC
> description at:
> 
> 
> http://msdn.microsoft.com/en-us/library/cc232117.aspx
> 
> Is there a different level that ou would like to call?
> 

Ok, good point...

Still...I think we ought to not base this API on on-the-wire formats. It
ought to use the standard container for passing file attributes around
(struct cifs_fattr). In most cases where we need to call these
functions we don't actually have a FILE_ALL_INFO and have to construct
one. Blech...

> On Fri, Nov 1, 2013 at 12:42 PM, Jeff Layton <jlayton@redhat.com> wrote:
> > On Fri,  1 Nov 2013 17:57:39 +0400
> > Pavel Shilovsky <piastryyy@gmail.com> wrote:
> >
> >> From: Pavel Shilovsky <piastry@etersoft.ru>
> >>
> >> Now we treat any reparse point as a symbolic link and map it to a Unix
> >> one that is not true in a common case due to many reparse point types
> >> supported by SMB servers.
> >>
> >> Distinguish reparse point types into two groups:
> >> 1) that can be accessed directly through a reparse point
> >> (junctions, deduplicated files, NFS symlinks);
> >> 2) that need to be processed manually (Windows symbolic links, DFS);
> >>
> >> and map only Windows symbolic links to Unix ones.
> >>
> >> Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru>
> >> ---
> >>  fs/cifs/cifsglob.h  |    2 +-
> >>  fs/cifs/inode.c     |   23 +++++++++++++----------
> >>  fs/cifs/readdir.c   |   40 ++++++++--------------------------------
> >>  fs/cifs/smb1ops.c   |   21 ++++++++++++++++++++-
> >>  fs/cifs/smb2inode.c |   16 ++++++++++++----
> >>  fs/cifs/smb2proto.h |    2 +-
> >>  6 files changed, 55 insertions(+), 49 deletions(-)
> >>
> >> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> >> index a67cf12..b688f2b 100644
> >> --- a/fs/cifs/cifsglob.h
> >> +++ b/fs/cifs/cifsglob.h
> >> @@ -261,7 +261,7 @@ struct smb_version_operations {
> >>       /* query path data from the server */
> >>       int (*query_path_info)(const unsigned int, struct cifs_tcon *,
> >>                              struct cifs_sb_info *, const char *,
> >> -                            FILE_ALL_INFO *, bool *);
> >> +                            FILE_ALL_INFO *, bool *, bool *);
> >
> >
> > This looks much better for performance, but I think it really shines a
> > light on what an abortion the query_path_info and query_file_info
> > operations are. They work with a FILE_ALL_INFO struct which is SMB1
> > specific, so for SMB2/3 we have to convert data to something that
> > doesn't even match the protocol.
> >
> > I think it would be best to base this fix on top of a patchset to fix
> > that properly. Those functions should be changed to pass data around in
> > a cifs_fattr. With that, you could just add a new cifs_fattr->flags
> > value and you wouldn't need to add this extra bool * argument.
> >
> >
> >>       /* query file data from the server */
> >>       int (*query_file_info)(const unsigned int, struct cifs_tcon *,
> >>                              struct cifs_fid *, FILE_ALL_INFO *);
> >> diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
> >> index 867b7cd..36f9ebb 100644
> >> --- a/fs/cifs/inode.c
> >> +++ b/fs/cifs/inode.c
> >> @@ -542,7 +542,8 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
> >>  /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
> >>  static void
> >>  cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
> >> -                    struct cifs_sb_info *cifs_sb, bool adjust_tz)
> >> +                    struct cifs_sb_info *cifs_sb, bool adjust_tz,
> >> +                    bool symlink)
> >>  {
> >>       struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> >>
> >> @@ -569,7 +570,11 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
> >>       fattr->cf_createtime = le64_to_cpu(info->CreationTime);
> >>
> >>       fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
> >> -     if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
> >> +
> >> +     if (symlink) {
> >> +             fattr->cf_mode = S_IFLNK;
> >> +             fattr->cf_dtype = DT_LNK;
> >> +     } else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
> >>               fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
> >>               fattr->cf_dtype = DT_DIR;
> >>               /*
> >> @@ -578,10 +583,6 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
> >>                */
> >>               if (!tcon->unix_ext)
> >>                       fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
> >> -     } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> >> -             fattr->cf_mode = S_IFLNK;
> >> -             fattr->cf_dtype = DT_LNK;
> >> -             fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
> >>       } else {
> >>               fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
> >>               fattr->cf_dtype = DT_REG;
> >> @@ -626,7 +627,8 @@ cifs_get_file_info(struct file *filp)
> >>       rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data);
> >>       switch (rc) {
> >>       case 0:
> >> -             cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false);
> >> +             cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false,
> >> +                                    false);
> >>               break;
> >>       case -EREMOTE:
> >>               cifs_create_dfs_fattr(&fattr, inode->i_sb);
> >> @@ -673,6 +675,7 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
> >>       bool adjust_tz = false;
> >>       struct cifs_fattr fattr;
> >>       struct cifs_search_info *srchinf = NULL;
> >> +     bool symlink = false;
> >>
> >>       tlink = cifs_sb_tlink(cifs_sb);
> >>       if (IS_ERR(tlink))
> >> @@ -702,12 +705,12 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
> >>               }
> >>               data = (FILE_ALL_INFO *)buf;
> >>               rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path,
> >> -                                               data, &adjust_tz);
> >> +                                               data, &adjust_tz, &symlink);
> >>       }
> >>
> >>       if (!rc) {
> >> -             cifs_all_info_to_fattr(&fattr, (FILE_ALL_INFO *)data, cifs_sb,
> >> -                                    adjust_tz);
> >> +             cifs_all_info_to_fattr(&fattr, data, cifs_sb, adjust_tz,
> >> +                                    symlink);
> >>       } else if (rc == -EREMOTE) {
> >>               cifs_create_dfs_fattr(&fattr, sb);
> >>               rc = 0;
> >> diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
> >> index 53a75f3..5940eca 100644
> >> --- a/fs/cifs/readdir.c
> >> +++ b/fs/cifs/readdir.c
> >> @@ -134,22 +134,6 @@ out:
> >>       dput(dentry);
> >>  }
> >>
> >> -/*
> >> - * Is it possible that this directory might turn out to be a DFS referral
> >> - * once we go to try and use it?
> >> - */
> >> -static bool
> >> -cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
> >> -{
> >> -#ifdef CONFIG_CIFS_DFS_UPCALL
> >> -     struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> >> -
> >> -     if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
> >> -             return true;
> >> -#endif
> >> -     return false;
> >> -}
> >> -
> >>  static void
> >>  cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
> >>  {
> >> @@ -159,27 +143,19 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
> >>       if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
> >>               fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
> >>               fattr->cf_dtype = DT_DIR;
> >> -             /*
> >> -              * Windows CIFS servers generally make DFS referrals look
> >> -              * like directories in FIND_* responses with the reparse
> >> -              * attribute flag also set (since DFS junctions are
> >> -              * reparse points). We must revalidate at least these
> >> -              * directory inodes before trying to use them (if
> >> -              * they are DFS we will get PATH_NOT_COVERED back
> >> -              * when queried directly and can then try to connect
> >> -              * to the DFS target)
> >> -              */
> >> -             if (cifs_dfs_is_possible(cifs_sb) &&
> >> -                 (fattr->cf_cifsattrs & ATTR_REPARSE))
> >> -                     fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> >> -     } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> >> -             fattr->cf_mode = S_IFLNK;
> >> -             fattr->cf_dtype = DT_LNK;
> >>       } else {
> >>               fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
> >>               fattr->cf_dtype = DT_REG;
> >>       }
> >>
> >> +     /*
> >> +      * We need to revalidate it further to make a decision about whether it
> >> +      * is a symbolic link, DFS referral or a reparse point with a direct
> >> +      * access like junctions, deduplicated files, NFS symlinks.
> >> +      */
> >> +     if (fattr->cf_cifsattrs & ATTR_REPARSE)
> >> +             fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> >> +
> >>       /* non-unix readdir doesn't provide nlink */
> >>       fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
> >>
> >> diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
> >> index ea99efe..47ab035 100644
> >> --- a/fs/cifs/smb1ops.c
> >> +++ b/fs/cifs/smb1ops.c
> >> @@ -534,10 +534,12 @@ cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
> >>  static int
> >>  cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>                    struct cifs_sb_info *cifs_sb, const char *full_path,
> >> -                  FILE_ALL_INFO *data, bool *adjustTZ)
> >> +                  FILE_ALL_INFO *data, bool *adjustTZ, bool *symlink)
> >>  {
> >>       int rc;
> >>
> >> +     *symlink = false;
> >> +
> >>       /* could do find first instead but this returns more info */
> >>       rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */,
> >>                             cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
> >> @@ -554,6 +556,23 @@ cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
> >>               *adjustTZ = true;
> >>       }
> >> +
> >> +     if (!rc && (le32_to_cpu(data->Attributes) & ATTR_REPARSE)) {
> >> +             int tmprc;
> >> +             int oplock = 0;
> >> +             __u16 netfid;
> >> +
> >> +             /* Need to check if this is a symbolic link or not */
> >> +             tmprc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
> >> +                              FILE_READ_ATTRIBUTES, 0, &netfid, &oplock,
> >> +                              NULL, cifs_sb->local_nls,
> >> +                     cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
> >> +             if (tmprc == -EOPNOTSUPP)
> >> +                     *symlink = true;
> >> +             else
> >> +                     CIFSSMBClose(xid, tcon, netfid);
> >> +     }
> >> +
> >>       return rc;
> >>  }
> >>
> >> diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c
> >> index 78ff88c..84c012a 100644
> >> --- a/fs/cifs/smb2inode.c
> >> +++ b/fs/cifs/smb2inode.c
> >> @@ -123,12 +123,13 @@ move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src)
> >>  int
> >>  smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>                    struct cifs_sb_info *cifs_sb, const char *full_path,
> >> -                  FILE_ALL_INFO *data, bool *adjust_tz)
> >> +                  FILE_ALL_INFO *data, bool *adjust_tz, bool *symlink)
> >>  {
> >>       int rc;
> >>       struct smb2_file_all_info *smb2_data;
> >>
> >>       *adjust_tz = false;
> >> +     *symlink = false;
> >>
> >>       smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + MAX_NAME * 2,
> >>                           GFP_KERNEL);
> >> @@ -136,9 +137,16 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>               return -ENOMEM;
> >>
> >>       rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> >> -                             FILE_READ_ATTRIBUTES, FILE_OPEN,
> >> -                             OPEN_REPARSE_POINT, smb2_data,
> >> -                             SMB2_OP_QUERY_INFO);
> >> +                             FILE_READ_ATTRIBUTES, FILE_OPEN, 0,
> >> +                             smb2_data, SMB2_OP_QUERY_INFO);
> >> +     if (rc == -EOPNOTSUPP) {
> >> +             *symlink = true;
> >> +             /* Failed on a symbolic link - query a reparse point info */
> >> +             rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> >> +                                     FILE_READ_ATTRIBUTES, FILE_OPEN,
> >> +                                     OPEN_REPARSE_POINT, smb2_data,
> >> +                                     SMB2_OP_QUERY_INFO);
> >> +     }
> >>       if (rc)
> >>               goto out;
> >>
> >> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
> >> index 313813e..b4eea10 100644
> >> --- a/fs/cifs/smb2proto.h
> >> +++ b/fs/cifs/smb2proto.h
> >> @@ -61,7 +61,7 @@ extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst,
> >>  extern int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>                               struct cifs_sb_info *cifs_sb,
> >>                               const char *full_path, FILE_ALL_INFO *data,
> >> -                             bool *adjust_tz);
> >> +                             bool *adjust_tz, bool *symlink);
> >>  extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
> >>                             const char *full_path, __u64 size,
> >>                             struct cifs_sb_info *cifs_sb, bool set_alloc);
> >
> >
> > --
> > Jeff Layton <jlayton@redhat.com>
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 
>
Jeff Layton Nov. 2, 2013, 12:10 p.m. UTC | #5
On Fri, 1 Nov 2013 12:49:47 -0500
Steve French <smfrench@gmail.com> wrote:

> FILE_ALL_INFO is not SMB1 specific, we request it in SMB2/SMB3 as well
> and it is still a common level.  See section 2.2.37 of MS-SMB2 or FSCC
> description at:
> 
> 
> http://msdn.microsoft.com/en-us/library/cc232117.aspx
> 
> Is there a different level that ou would like to call?
> 

Now that I look, I'm not sure that this is the case. If we get a
FILE_ALL_INFO from the server, what do we need move_smb2_info_to_cifs()
for?

Either way, this API would be *much* cleaner if we didn't try to use
on-the-wire formats to pass inode info around. That's what a cifs_fattr
is for, after all...

> On Fri, Nov 1, 2013 at 12:42 PM, Jeff Layton <jlayton@redhat.com> wrote:
> > On Fri,  1 Nov 2013 17:57:39 +0400
> > Pavel Shilovsky <piastryyy@gmail.com> wrote:
> >
> >> From: Pavel Shilovsky <piastry@etersoft.ru>
> >>
> >> Now we treat any reparse point as a symbolic link and map it to a Unix
> >> one that is not true in a common case due to many reparse point types
> >> supported by SMB servers.
> >>
> >> Distinguish reparse point types into two groups:
> >> 1) that can be accessed directly through a reparse point
> >> (junctions, deduplicated files, NFS symlinks);
> >> 2) that need to be processed manually (Windows symbolic links, DFS);
> >>
> >> and map only Windows symbolic links to Unix ones.
> >>
> >> Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru>
> >> ---
> >>  fs/cifs/cifsglob.h  |    2 +-
> >>  fs/cifs/inode.c     |   23 +++++++++++++----------
> >>  fs/cifs/readdir.c   |   40 ++++++++--------------------------------
> >>  fs/cifs/smb1ops.c   |   21 ++++++++++++++++++++-
> >>  fs/cifs/smb2inode.c |   16 ++++++++++++----
> >>  fs/cifs/smb2proto.h |    2 +-
> >>  6 files changed, 55 insertions(+), 49 deletions(-)
> >>
> >> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> >> index a67cf12..b688f2b 100644
> >> --- a/fs/cifs/cifsglob.h
> >> +++ b/fs/cifs/cifsglob.h
> >> @@ -261,7 +261,7 @@ struct smb_version_operations {
> >>       /* query path data from the server */
> >>       int (*query_path_info)(const unsigned int, struct cifs_tcon *,
> >>                              struct cifs_sb_info *, const char *,
> >> -                            FILE_ALL_INFO *, bool *);
> >> +                            FILE_ALL_INFO *, bool *, bool *);
> >
> >
> > This looks much better for performance, but I think it really shines a
> > light on what an abortion the query_path_info and query_file_info
> > operations are. They work with a FILE_ALL_INFO struct which is SMB1
> > specific, so for SMB2/3 we have to convert data to something that
> > doesn't even match the protocol.
> >
> > I think it would be best to base this fix on top of a patchset to fix
> > that properly. Those functions should be changed to pass data around in
> > a cifs_fattr. With that, you could just add a new cifs_fattr->flags
> > value and you wouldn't need to add this extra bool * argument.
> >
> >
> >>       /* query file data from the server */
> >>       int (*query_file_info)(const unsigned int, struct cifs_tcon *,
> >>                              struct cifs_fid *, FILE_ALL_INFO *);
> >> diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
> >> index 867b7cd..36f9ebb 100644
> >> --- a/fs/cifs/inode.c
> >> +++ b/fs/cifs/inode.c
> >> @@ -542,7 +542,8 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
> >>  /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
> >>  static void
> >>  cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
> >> -                    struct cifs_sb_info *cifs_sb, bool adjust_tz)
> >> +                    struct cifs_sb_info *cifs_sb, bool adjust_tz,
> >> +                    bool symlink)
> >>  {
> >>       struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> >>
> >> @@ -569,7 +570,11 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
> >>       fattr->cf_createtime = le64_to_cpu(info->CreationTime);
> >>
> >>       fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
> >> -     if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
> >> +
> >> +     if (symlink) {
> >> +             fattr->cf_mode = S_IFLNK;
> >> +             fattr->cf_dtype = DT_LNK;
> >> +     } else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
> >>               fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
> >>               fattr->cf_dtype = DT_DIR;
> >>               /*
> >> @@ -578,10 +583,6 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
> >>                */
> >>               if (!tcon->unix_ext)
> >>                       fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
> >> -     } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> >> -             fattr->cf_mode = S_IFLNK;
> >> -             fattr->cf_dtype = DT_LNK;
> >> -             fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
> >>       } else {
> >>               fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
> >>               fattr->cf_dtype = DT_REG;
> >> @@ -626,7 +627,8 @@ cifs_get_file_info(struct file *filp)
> >>       rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data);
> >>       switch (rc) {
> >>       case 0:
> >> -             cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false);
> >> +             cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false,
> >> +                                    false);
> >>               break;
> >>       case -EREMOTE:
> >>               cifs_create_dfs_fattr(&fattr, inode->i_sb);
> >> @@ -673,6 +675,7 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
> >>       bool adjust_tz = false;
> >>       struct cifs_fattr fattr;
> >>       struct cifs_search_info *srchinf = NULL;
> >> +     bool symlink = false;
> >>
> >>       tlink = cifs_sb_tlink(cifs_sb);
> >>       if (IS_ERR(tlink))
> >> @@ -702,12 +705,12 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
> >>               }
> >>               data = (FILE_ALL_INFO *)buf;
> >>               rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path,
> >> -                                               data, &adjust_tz);
> >> +                                               data, &adjust_tz, &symlink);
> >>       }
> >>
> >>       if (!rc) {
> >> -             cifs_all_info_to_fattr(&fattr, (FILE_ALL_INFO *)data, cifs_sb,
> >> -                                    adjust_tz);
> >> +             cifs_all_info_to_fattr(&fattr, data, cifs_sb, adjust_tz,
> >> +                                    symlink);
> >>       } else if (rc == -EREMOTE) {
> >>               cifs_create_dfs_fattr(&fattr, sb);
> >>               rc = 0;
> >> diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
> >> index 53a75f3..5940eca 100644
> >> --- a/fs/cifs/readdir.c
> >> +++ b/fs/cifs/readdir.c
> >> @@ -134,22 +134,6 @@ out:
> >>       dput(dentry);
> >>  }
> >>
> >> -/*
> >> - * Is it possible that this directory might turn out to be a DFS referral
> >> - * once we go to try and use it?
> >> - */
> >> -static bool
> >> -cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
> >> -{
> >> -#ifdef CONFIG_CIFS_DFS_UPCALL
> >> -     struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> >> -
> >> -     if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
> >> -             return true;
> >> -#endif
> >> -     return false;
> >> -}
> >> -
> >>  static void
> >>  cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
> >>  {
> >> @@ -159,27 +143,19 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
> >>       if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
> >>               fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
> >>               fattr->cf_dtype = DT_DIR;
> >> -             /*
> >> -              * Windows CIFS servers generally make DFS referrals look
> >> -              * like directories in FIND_* responses with the reparse
> >> -              * attribute flag also set (since DFS junctions are
> >> -              * reparse points). We must revalidate at least these
> >> -              * directory inodes before trying to use them (if
> >> -              * they are DFS we will get PATH_NOT_COVERED back
> >> -              * when queried directly and can then try to connect
> >> -              * to the DFS target)
> >> -              */
> >> -             if (cifs_dfs_is_possible(cifs_sb) &&
> >> -                 (fattr->cf_cifsattrs & ATTR_REPARSE))
> >> -                     fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> >> -     } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> >> -             fattr->cf_mode = S_IFLNK;
> >> -             fattr->cf_dtype = DT_LNK;
> >>       } else {
> >>               fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
> >>               fattr->cf_dtype = DT_REG;
> >>       }
> >>
> >> +     /*
> >> +      * We need to revalidate it further to make a decision about whether it
> >> +      * is a symbolic link, DFS referral or a reparse point with a direct
> >> +      * access like junctions, deduplicated files, NFS symlinks.
> >> +      */
> >> +     if (fattr->cf_cifsattrs & ATTR_REPARSE)
> >> +             fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> >> +
> >>       /* non-unix readdir doesn't provide nlink */
> >>       fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
> >>
> >> diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
> >> index ea99efe..47ab035 100644
> >> --- a/fs/cifs/smb1ops.c
> >> +++ b/fs/cifs/smb1ops.c
> >> @@ -534,10 +534,12 @@ cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
> >>  static int
> >>  cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>                    struct cifs_sb_info *cifs_sb, const char *full_path,
> >> -                  FILE_ALL_INFO *data, bool *adjustTZ)
> >> +                  FILE_ALL_INFO *data, bool *adjustTZ, bool *symlink)
> >>  {
> >>       int rc;
> >>
> >> +     *symlink = false;
> >> +
> >>       /* could do find first instead but this returns more info */
> >>       rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */,
> >>                             cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
> >> @@ -554,6 +556,23 @@ cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
> >>               *adjustTZ = true;
> >>       }
> >> +
> >> +     if (!rc && (le32_to_cpu(data->Attributes) & ATTR_REPARSE)) {
> >> +             int tmprc;
> >> +             int oplock = 0;
> >> +             __u16 netfid;
> >> +
> >> +             /* Need to check if this is a symbolic link or not */
> >> +             tmprc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
> >> +                              FILE_READ_ATTRIBUTES, 0, &netfid, &oplock,
> >> +                              NULL, cifs_sb->local_nls,
> >> +                     cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
> >> +             if (tmprc == -EOPNOTSUPP)
> >> +                     *symlink = true;
> >> +             else
> >> +                     CIFSSMBClose(xid, tcon, netfid);
> >> +     }
> >> +
> >>       return rc;
> >>  }
> >>
> >> diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c
> >> index 78ff88c..84c012a 100644
> >> --- a/fs/cifs/smb2inode.c
> >> +++ b/fs/cifs/smb2inode.c
> >> @@ -123,12 +123,13 @@ move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src)
> >>  int
> >>  smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>                    struct cifs_sb_info *cifs_sb, const char *full_path,
> >> -                  FILE_ALL_INFO *data, bool *adjust_tz)
> >> +                  FILE_ALL_INFO *data, bool *adjust_tz, bool *symlink)
> >>  {
> >>       int rc;
> >>       struct smb2_file_all_info *smb2_data;
> >>
> >>       *adjust_tz = false;
> >> +     *symlink = false;
> >>
> >>       smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + MAX_NAME * 2,
> >>                           GFP_KERNEL);
> >> @@ -136,9 +137,16 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>               return -ENOMEM;
> >>
> >>       rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> >> -                             FILE_READ_ATTRIBUTES, FILE_OPEN,
> >> -                             OPEN_REPARSE_POINT, smb2_data,
> >> -                             SMB2_OP_QUERY_INFO);
> >> +                             FILE_READ_ATTRIBUTES, FILE_OPEN, 0,
> >> +                             smb2_data, SMB2_OP_QUERY_INFO);
> >> +     if (rc == -EOPNOTSUPP) {
> >> +             *symlink = true;
> >> +             /* Failed on a symbolic link - query a reparse point info */
> >> +             rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> >> +                                     FILE_READ_ATTRIBUTES, FILE_OPEN,
> >> +                                     OPEN_REPARSE_POINT, smb2_data,
> >> +                                     SMB2_OP_QUERY_INFO);
> >> +     }
> >>       if (rc)
> >>               goto out;
> >>
> >> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
> >> index 313813e..b4eea10 100644
> >> --- a/fs/cifs/smb2proto.h
> >> +++ b/fs/cifs/smb2proto.h
> >> @@ -61,7 +61,7 @@ extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst,
> >>  extern int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
> >>                               struct cifs_sb_info *cifs_sb,
> >>                               const char *full_path, FILE_ALL_INFO *data,
> >> -                             bool *adjust_tz);
> >> +                             bool *adjust_tz, bool *symlink);
> >>  extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
> >>                             const char *full_path, __u64 size,
> >>                             struct cifs_sb_info *cifs_sb, bool set_alloc);
> >
> >
> > --
> > Jeff Layton <jlayton@redhat.com>
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 
>
Pavel Shilovsky Nov. 2, 2013, 7:42 p.m. UTC | #6
2013/11/1 Shirish Pargaonkar <shirishpargaonkar@gmail.com>:
> Pavel, does this handle a case where it could be symbolic link
> to a directory?

Shirish, yes - it supports symbolic links to directories as well as to
files. It treats them both as Unix symbolic links without any
difference.
Pavel Shilovsky Nov. 2, 2013, 7:47 p.m. UTC | #7
2013/11/1 Jeff Layton <jlayton@redhat.com>:
> This looks much better for performance, but I think it really shines a
> light on what an abortion the query_path_info and query_file_info
> operations are. They work with a FILE_ALL_INFO struct which is SMB1
> specific, so for SMB2/3 we have to convert data to something that
> doesn't even match the protocol.
>
> I think it would be best to base this fix on top of a patchset to fix
> that properly. Those functions should be changed to pass data around in
> a cifs_fattr. With that, you could just add a new cifs_fattr->flags
> value and you wouldn't need to add this extra bool * argument.

I agree that using cifs_fattr structure rather than FILE_ALL_INFO
simplifies things and makes the code cleaner. But we have a problem in
the 3.11 stable branch and I think we should fix it without many
patches (code changes). We can apply this patch for stable and then
convert the code to use cifs_fattr structure in query path/file info
in mainline. Right?
Jeff Layton Nov. 3, 2013, 1:14 a.m. UTC | #8
On Sat, 2 Nov 2013 23:47:16 +0400
Pavel Shilovsky <piastryyy@gmail.com> wrote:

> 2013/11/1 Jeff Layton <jlayton@redhat.com>:
> > This looks much better for performance, but I think it really shines a
> > light on what an abortion the query_path_info and query_file_info
> > operations are. They work with a FILE_ALL_INFO struct which is SMB1
> > specific, so for SMB2/3 we have to convert data to something that
> > doesn't even match the protocol.
> >
> > I think it would be best to base this fix on top of a patchset to fix
> > that properly. Those functions should be changed to pass data around in
> > a cifs_fattr. With that, you could just add a new cifs_fattr->flags
> > value and you wouldn't need to add this extra bool * argument.
> 
> I agree that using cifs_fattr structure rather than FILE_ALL_INFO
> simplifies things and makes the code cleaner. But we have a problem in
> the 3.11 stable branch and I think we should fix it without many
> patches (code changes). We can apply this patch for stable and then
> convert the code to use cifs_fattr structure in query path/file info
> in mainline. Right?
> 

If you intend to push a fix to stable for this, then that sounds like
the best approach. It will make cleaning up the API harder, but as long
as you're ok with that...
Pavel Shilovsky Nov. 4, 2013, 6:50 p.m. UTC | #9
2013/11/3 Jeff Layton <jlayton@redhat.com>:
> If you intend to push a fix to stable for this, then that sounds like
> the best approach. It will make cleaning up the API harder, but as long
> as you're ok with that...

Yes - it was targeted for stable as well. Any acks from your side?

Steve, do you have any objections to apply this patch (with Joao's
"Reported-and-tested-by: Joao Correia <joaomiguelcorreia@gmail.com>"
tag)?
Steve French Nov. 4, 2013, 6:54 p.m. UTC | #10
a little large for stable but not sure yet what could be done to make it smaller

On Mon, Nov 4, 2013 at 12:50 PM, Pavel Shilovsky <piastryyy@gmail.com> wrote:
> 2013/11/3 Jeff Layton <jlayton@redhat.com>:
>> If you intend to push a fix to stable for this, then that sounds like
>> the best approach. It will make cleaning up the API harder, but as long
>> as you're ok with that...
>
> Yes - it was targeted for stable as well. Any acks from your side?
>
> Steve, do you have any objections to apply this patch (with Joao's
> "Reported-and-tested-by: Joao Correia <joaomiguelcorreia@gmail.com>"
> tag)?
>
> --
> Best regards,
> Pavel Shilovsky.
Jeff Layton Nov. 5, 2013, 7:31 p.m. UTC | #11
On Fri,  1 Nov 2013 17:57:39 +0400
Pavel Shilovsky <piastryyy@gmail.com> wrote:

> From: Pavel Shilovsky <piastry@etersoft.ru>
> 
> Now we treat any reparse point as a symbolic link and map it to a Unix
> one that is not true in a common case due to many reparse point types
> supported by SMB servers.
> 
> Distinguish reparse point types into two groups:
> 1) that can be accessed directly through a reparse point
> (junctions, deduplicated files, NFS symlinks);
> 2) that need to be processed manually (Windows symbolic links, DFS);
> 
> and map only Windows symbolic links to Unix ones.
> 
> Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru>
> ---
>  fs/cifs/cifsglob.h  |    2 +-
>  fs/cifs/inode.c     |   23 +++++++++++++----------
>  fs/cifs/readdir.c   |   40 ++++++++--------------------------------
>  fs/cifs/smb1ops.c   |   21 ++++++++++++++++++++-
>  fs/cifs/smb2inode.c |   16 ++++++++++++----
>  fs/cifs/smb2proto.h |    2 +-
>  6 files changed, 55 insertions(+), 49 deletions(-)
> 
> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> index a67cf12..b688f2b 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -261,7 +261,7 @@ struct smb_version_operations {
>  	/* query path data from the server */
>  	int (*query_path_info)(const unsigned int, struct cifs_tcon *,
>  			       struct cifs_sb_info *, const char *,
> -			       FILE_ALL_INFO *, bool *);
> +			       FILE_ALL_INFO *, bool *, bool *);
>  	/* query file data from the server */
>  	int (*query_file_info)(const unsigned int, struct cifs_tcon *,
>  			       struct cifs_fid *, FILE_ALL_INFO *);
> diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
> index 867b7cd..36f9ebb 100644
> --- a/fs/cifs/inode.c
> +++ b/fs/cifs/inode.c
> @@ -542,7 +542,8 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
>  /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
>  static void
>  cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
> -		       struct cifs_sb_info *cifs_sb, bool adjust_tz)
> +		       struct cifs_sb_info *cifs_sb, bool adjust_tz,
> +		       bool symlink)
>  {
>  	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
>  
> @@ -569,7 +570,11 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>  	fattr->cf_createtime = le64_to_cpu(info->CreationTime);
>  
>  	fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
> -	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
> +
> +	if (symlink) {
> +		fattr->cf_mode = S_IFLNK;
> +		fattr->cf_dtype = DT_LNK;
> +	} else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>  		fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
>  		fattr->cf_dtype = DT_DIR;
>  		/*
> @@ -578,10 +583,6 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
>  		 */
>  		if (!tcon->unix_ext)
>  			fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
> -	} else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> -		fattr->cf_mode = S_IFLNK;
> -		fattr->cf_dtype = DT_LNK;
> -		fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
>  	} else {
>  		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>  		fattr->cf_dtype = DT_REG;
> @@ -626,7 +627,8 @@ cifs_get_file_info(struct file *filp)
>  	rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data);
>  	switch (rc) {
>  	case 0:
> -		cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false);
> +		cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false,
> +				       false);
>  		break;
>  	case -EREMOTE:
>  		cifs_create_dfs_fattr(&fattr, inode->i_sb);
> @@ -673,6 +675,7 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
>  	bool adjust_tz = false;
>  	struct cifs_fattr fattr;
>  	struct cifs_search_info *srchinf = NULL;
> +	bool symlink = false;
>  
>  	tlink = cifs_sb_tlink(cifs_sb);
>  	if (IS_ERR(tlink))
> @@ -702,12 +705,12 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
>  		}
>  		data = (FILE_ALL_INFO *)buf;
>  		rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path,
> -						  data, &adjust_tz);
> +						  data, &adjust_tz, &symlink);
>  	}
>  
>  	if (!rc) {
> -		cifs_all_info_to_fattr(&fattr, (FILE_ALL_INFO *)data, cifs_sb,
> -				       adjust_tz);
> +		cifs_all_info_to_fattr(&fattr, data, cifs_sb, adjust_tz,
> +				       symlink);
>  	} else if (rc == -EREMOTE) {
>  		cifs_create_dfs_fattr(&fattr, sb);
>  		rc = 0;
> diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
> index 53a75f3..5940eca 100644
> --- a/fs/cifs/readdir.c
> +++ b/fs/cifs/readdir.c
> @@ -134,22 +134,6 @@ out:
>  	dput(dentry);
>  }
>  
> -/*
> - * Is it possible that this directory might turn out to be a DFS referral
> - * once we go to try and use it?
> - */
> -static bool
> -cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
> -{
> -#ifdef CONFIG_CIFS_DFS_UPCALL
> -	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
> -
> -	if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
> -		return true;
> -#endif
> -	return false;
> -}
> -
>  static void
>  cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>  {
> @@ -159,27 +143,19 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
>  	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
>  		fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
>  		fattr->cf_dtype = DT_DIR;
> -		/*
> -		 * Windows CIFS servers generally make DFS referrals look
> -		 * like directories in FIND_* responses with the reparse
> -		 * attribute flag also set (since DFS junctions are
> -		 * reparse points). We must revalidate at least these
> -		 * directory inodes before trying to use them (if
> -		 * they are DFS we will get PATH_NOT_COVERED back
> -		 * when queried directly and can then try to connect
> -		 * to the DFS target)
> -		 */
> -		if (cifs_dfs_is_possible(cifs_sb) &&
> -		    (fattr->cf_cifsattrs & ATTR_REPARSE))
> -			fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> -	} else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
> -		fattr->cf_mode = S_IFLNK;
> -		fattr->cf_dtype = DT_LNK;
>  	} else {
>  		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
>  		fattr->cf_dtype = DT_REG;
>  	}
>  
> +	/*
> +	 * We need to revalidate it further to make a decision about whether it
> +	 * is a symbolic link, DFS referral or a reparse point with a direct
> +	 * access like junctions, deduplicated files, NFS symlinks.
> +	 */
> +	if (fattr->cf_cifsattrs & ATTR_REPARSE)
> +		fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
> +
>  	/* non-unix readdir doesn't provide nlink */
>  	fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
>  
> diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
> index ea99efe..47ab035 100644
> --- a/fs/cifs/smb1ops.c
> +++ b/fs/cifs/smb1ops.c
> @@ -534,10 +534,12 @@ cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
>  static int
>  cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  		     struct cifs_sb_info *cifs_sb, const char *full_path,
> -		     FILE_ALL_INFO *data, bool *adjustTZ)
> +		     FILE_ALL_INFO *data, bool *adjustTZ, bool *symlink)
>  {
>  	int rc;
>  
> +	*symlink = false;
> +
>  	/* could do find first instead but this returns more info */
>  	rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */,
>  			      cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
> @@ -554,6 +556,23 @@ cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  						CIFS_MOUNT_MAP_SPECIAL_CHR);
>  		*adjustTZ = true;
>  	}
> +
> +	if (!rc && (le32_to_cpu(data->Attributes) & ATTR_REPARSE)) {
> +		int tmprc;
> +		int oplock = 0;
> +		__u16 netfid;
> +
> +		/* Need to check if this is a symbolic link or not */
> +		tmprc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
> +				 FILE_READ_ATTRIBUTES, 0, &netfid, &oplock,
> +				 NULL, cifs_sb->local_nls,
> +			cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
> +		if (tmprc == -EOPNOTSUPP)
> +			*symlink = true;
> +		else
> +			CIFSSMBClose(xid, tcon, netfid);
> +	}
> +
>  	return rc;
>  }
>  
> diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c
> index 78ff88c..84c012a 100644
> --- a/fs/cifs/smb2inode.c
> +++ b/fs/cifs/smb2inode.c
> @@ -123,12 +123,13 @@ move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src)
>  int
>  smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  		     struct cifs_sb_info *cifs_sb, const char *full_path,
> -		     FILE_ALL_INFO *data, bool *adjust_tz)
> +		     FILE_ALL_INFO *data, bool *adjust_tz, bool *symlink)
>  {
>  	int rc;
>  	struct smb2_file_all_info *smb2_data;
>  
>  	*adjust_tz = false;
> +	*symlink = false;
>  
>  	smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + MAX_NAME * 2,
>  			    GFP_KERNEL);
> @@ -136,9 +137,16 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  		return -ENOMEM;
>  
>  	rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> -				FILE_READ_ATTRIBUTES, FILE_OPEN,
> -				OPEN_REPARSE_POINT, smb2_data,
> -				SMB2_OP_QUERY_INFO);
> +				FILE_READ_ATTRIBUTES, FILE_OPEN, 0,
> +				smb2_data, SMB2_OP_QUERY_INFO);
> +	if (rc == -EOPNOTSUPP) {
> +		*symlink = true;
> +		/* Failed on a symbolic link - query a reparse point info */
> +		rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
> +					FILE_READ_ATTRIBUTES, FILE_OPEN,
> +					OPEN_REPARSE_POINT, smb2_data,
> +					SMB2_OP_QUERY_INFO);
> +	}
>  	if (rc)
>  		goto out;
>  
> diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
> index 313813e..b4eea10 100644
> --- a/fs/cifs/smb2proto.h
> +++ b/fs/cifs/smb2proto.h
> @@ -61,7 +61,7 @@ extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst,
>  extern int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
>  				struct cifs_sb_info *cifs_sb,
>  				const char *full_path, FILE_ALL_INFO *data,
> -				bool *adjust_tz);
> +				bool *adjust_tz, bool *symlink);
>  extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
>  			      const char *full_path, __u64 size,
>  			      struct cifs_sb_info *cifs_sb, bool set_alloc);


Looks reasonable for an interim fix I guess. I'm not thrilled with it
since it doesn't help the code overall, but I guess we can live with it
until we can clean up the API.

Acked-by: Jeff Layton <jlayton@redhat.com>
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index a67cf12..b688f2b 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -261,7 +261,7 @@  struct smb_version_operations {
 	/* query path data from the server */
 	int (*query_path_info)(const unsigned int, struct cifs_tcon *,
 			       struct cifs_sb_info *, const char *,
-			       FILE_ALL_INFO *, bool *);
+			       FILE_ALL_INFO *, bool *, bool *);
 	/* query file data from the server */
 	int (*query_file_info)(const unsigned int, struct cifs_tcon *,
 			       struct cifs_fid *, FILE_ALL_INFO *);
diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
index 867b7cd..36f9ebb 100644
--- a/fs/cifs/inode.c
+++ b/fs/cifs/inode.c
@@ -542,7 +542,8 @@  static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
 /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
 static void
 cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
-		       struct cifs_sb_info *cifs_sb, bool adjust_tz)
+		       struct cifs_sb_info *cifs_sb, bool adjust_tz,
+		       bool symlink)
 {
 	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
 
@@ -569,7 +570,11 @@  cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
 	fattr->cf_createtime = le64_to_cpu(info->CreationTime);
 
 	fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
-	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
+
+	if (symlink) {
+		fattr->cf_mode = S_IFLNK;
+		fattr->cf_dtype = DT_LNK;
+	} else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
 		fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
 		fattr->cf_dtype = DT_DIR;
 		/*
@@ -578,10 +583,6 @@  cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
 		 */
 		if (!tcon->unix_ext)
 			fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
-	} else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
-		fattr->cf_mode = S_IFLNK;
-		fattr->cf_dtype = DT_LNK;
-		fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
 	} else {
 		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
 		fattr->cf_dtype = DT_REG;
@@ -626,7 +627,8 @@  cifs_get_file_info(struct file *filp)
 	rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data);
 	switch (rc) {
 	case 0:
-		cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false);
+		cifs_all_info_to_fattr(&fattr, &find_data, cifs_sb, false,
+				       false);
 		break;
 	case -EREMOTE:
 		cifs_create_dfs_fattr(&fattr, inode->i_sb);
@@ -673,6 +675,7 @@  cifs_get_inode_info(struct inode **inode, const char *full_path,
 	bool adjust_tz = false;
 	struct cifs_fattr fattr;
 	struct cifs_search_info *srchinf = NULL;
+	bool symlink = false;
 
 	tlink = cifs_sb_tlink(cifs_sb);
 	if (IS_ERR(tlink))
@@ -702,12 +705,12 @@  cifs_get_inode_info(struct inode **inode, const char *full_path,
 		}
 		data = (FILE_ALL_INFO *)buf;
 		rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path,
-						  data, &adjust_tz);
+						  data, &adjust_tz, &symlink);
 	}
 
 	if (!rc) {
-		cifs_all_info_to_fattr(&fattr, (FILE_ALL_INFO *)data, cifs_sb,
-				       adjust_tz);
+		cifs_all_info_to_fattr(&fattr, data, cifs_sb, adjust_tz,
+				       symlink);
 	} else if (rc == -EREMOTE) {
 		cifs_create_dfs_fattr(&fattr, sb);
 		rc = 0;
diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
index 53a75f3..5940eca 100644
--- a/fs/cifs/readdir.c
+++ b/fs/cifs/readdir.c
@@ -134,22 +134,6 @@  out:
 	dput(dentry);
 }
 
-/*
- * Is it possible that this directory might turn out to be a DFS referral
- * once we go to try and use it?
- */
-static bool
-cifs_dfs_is_possible(struct cifs_sb_info *cifs_sb)
-{
-#ifdef CONFIG_CIFS_DFS_UPCALL
-	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
-
-	if (tcon->Flags & SMB_SHARE_IS_IN_DFS)
-		return true;
-#endif
-	return false;
-}
-
 static void
 cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
 {
@@ -159,27 +143,19 @@  cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
 	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
 		fattr->cf_mode = S_IFDIR | cifs_sb->mnt_dir_mode;
 		fattr->cf_dtype = DT_DIR;
-		/*
-		 * Windows CIFS servers generally make DFS referrals look
-		 * like directories in FIND_* responses with the reparse
-		 * attribute flag also set (since DFS junctions are
-		 * reparse points). We must revalidate at least these
-		 * directory inodes before trying to use them (if
-		 * they are DFS we will get PATH_NOT_COVERED back
-		 * when queried directly and can then try to connect
-		 * to the DFS target)
-		 */
-		if (cifs_dfs_is_possible(cifs_sb) &&
-		    (fattr->cf_cifsattrs & ATTR_REPARSE))
-			fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
-	} else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
-		fattr->cf_mode = S_IFLNK;
-		fattr->cf_dtype = DT_LNK;
 	} else {
 		fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
 		fattr->cf_dtype = DT_REG;
 	}
 
+	/*
+	 * We need to revalidate it further to make a decision about whether it
+	 * is a symbolic link, DFS referral or a reparse point with a direct
+	 * access like junctions, deduplicated files, NFS symlinks.
+	 */
+	if (fattr->cf_cifsattrs & ATTR_REPARSE)
+		fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
+
 	/* non-unix readdir doesn't provide nlink */
 	fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
 
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index ea99efe..47ab035 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -534,10 +534,12 @@  cifs_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
 static int
 cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
 		     struct cifs_sb_info *cifs_sb, const char *full_path,
-		     FILE_ALL_INFO *data, bool *adjustTZ)
+		     FILE_ALL_INFO *data, bool *adjustTZ, bool *symlink)
 {
 	int rc;
 
+	*symlink = false;
+
 	/* could do find first instead but this returns more info */
 	rc = CIFSSMBQPathInfo(xid, tcon, full_path, data, 0 /* not legacy */,
 			      cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
@@ -554,6 +556,23 @@  cifs_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
 						CIFS_MOUNT_MAP_SPECIAL_CHR);
 		*adjustTZ = true;
 	}
+
+	if (!rc && (le32_to_cpu(data->Attributes) & ATTR_REPARSE)) {
+		int tmprc;
+		int oplock = 0;
+		__u16 netfid;
+
+		/* Need to check if this is a symbolic link or not */
+		tmprc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
+				 FILE_READ_ATTRIBUTES, 0, &netfid, &oplock,
+				 NULL, cifs_sb->local_nls,
+			cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
+		if (tmprc == -EOPNOTSUPP)
+			*symlink = true;
+		else
+			CIFSSMBClose(xid, tcon, netfid);
+	}
+
 	return rc;
 }
 
diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c
index 78ff88c..84c012a 100644
--- a/fs/cifs/smb2inode.c
+++ b/fs/cifs/smb2inode.c
@@ -123,12 +123,13 @@  move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src)
 int
 smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
 		     struct cifs_sb_info *cifs_sb, const char *full_path,
-		     FILE_ALL_INFO *data, bool *adjust_tz)
+		     FILE_ALL_INFO *data, bool *adjust_tz, bool *symlink)
 {
 	int rc;
 	struct smb2_file_all_info *smb2_data;
 
 	*adjust_tz = false;
+	*symlink = false;
 
 	smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + MAX_NAME * 2,
 			    GFP_KERNEL);
@@ -136,9 +137,16 @@  smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
 		return -ENOMEM;
 
 	rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
-				FILE_READ_ATTRIBUTES, FILE_OPEN,
-				OPEN_REPARSE_POINT, smb2_data,
-				SMB2_OP_QUERY_INFO);
+				FILE_READ_ATTRIBUTES, FILE_OPEN, 0,
+				smb2_data, SMB2_OP_QUERY_INFO);
+	if (rc == -EOPNOTSUPP) {
+		*symlink = true;
+		/* Failed on a symbolic link - query a reparse point info */
+		rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
+					FILE_READ_ATTRIBUTES, FILE_OPEN,
+					OPEN_REPARSE_POINT, smb2_data,
+					SMB2_OP_QUERY_INFO);
+	}
 	if (rc)
 		goto out;
 
diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
index 313813e..b4eea10 100644
--- a/fs/cifs/smb2proto.h
+++ b/fs/cifs/smb2proto.h
@@ -61,7 +61,7 @@  extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst,
 extern int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
 				struct cifs_sb_info *cifs_sb,
 				const char *full_path, FILE_ALL_INFO *data,
-				bool *adjust_tz);
+				bool *adjust_tz, bool *symlink);
 extern int smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
 			      const char *full_path, __u64 size,
 			      struct cifs_sb_info *cifs_sb, bool set_alloc);