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

Commit eb85d94b authored by Pavel Shilovsky's avatar Pavel Shilovsky Committed by Steve French
Browse files

CIFS: Fix symbolic links usage



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.

Cc: <stable@vger.kernel.org>
Acked-by: default avatarJeff Layton <jlayton@redhat.com>
Reported-and-tested-by: default avatarJoao Correia <joaomiguelcorreia@gmail.com>
Signed-off-by: default avatarPavel Shilovsky <piastry@etersoft.ru>
Signed-off-by: default avatarSteve French <smfrench@gmail.com>
parent 6c86ae29
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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 *);
+13 −10
Original line number Diff line number Diff line
@@ -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;
+8 −32
Original line number Diff line number Diff line
@@ -134,22 +134,6 @@ cifs_prime_dcache(struct dentry *parent, struct qstr *name,
	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;

+20 −1
Original line number Diff line number Diff line
@@ -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;
}

+12 −4
Original line number Diff line number Diff line
@@ -123,22 +123,30 @@ 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);
	if (smb2_data == NULL)
		return -ENOMEM;

	rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
				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;

Loading