Commit 5f71ebc4 authored by Paulo Alcantara's avatar Paulo Alcantara Committed by Steve French
Browse files

smb: client: parse reparse point flag in create response



Check for reparse point flag on query info calls as specified in
MS-SMB2 2.2.14.

Signed-off-by: default avatarPaulo Alcantara (SUSE) <pc@manguebit.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 348a04a8
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -199,6 +199,10 @@ struct cifs_open_info_data {
	};
};

#define cifs_open_data_reparse(d) \
	((d)->reparse_point || \
	 (le32_to_cpu((d)->fi.Attributes) & ATTR_REPARSE))

static inline void cifs_free_open_info(struct cifs_open_info_data *data)
{
	kfree(data->symlink_target);
+3 −0
Original line number Diff line number Diff line
@@ -207,6 +207,9 @@ extern struct inode *cifs_iget(struct super_block *sb,
int cifs_get_inode_info(struct inode **inode, const char *full_path,
			struct cifs_open_info_data *data, struct super_block *sb, int xid,
			const struct cifs_fid *fid);
bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
				 struct cifs_fattr *fattr,
				 u32 tag);
extern int smb311_posix_get_inode_info(struct inode **pinode, const char *search_path,
			struct super_block *sb, unsigned int xid);
extern int cifs_get_inode_info_unix(struct inode **pinode,
+83 −38
Original line number Diff line number Diff line
@@ -687,6 +687,43 @@ static void smb311_posix_info_to_fattr(struct cifs_fattr *fattr,
		fattr->cf_mode, fattr->cf_uniqueid, fattr->cf_nlink);
}

bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
				 struct cifs_fattr *fattr,
				 u32 tag)
{
	switch (tag) {
	case IO_REPARSE_TAG_LX_SYMLINK:
		fattr->cf_mode |= S_IFLNK | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_LNK;
		break;
	case IO_REPARSE_TAG_LX_FIFO:
		fattr->cf_mode |= S_IFIFO | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_FIFO;
		break;
	case IO_REPARSE_TAG_AF_UNIX:
		fattr->cf_mode |= S_IFSOCK | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_SOCK;
		break;
	case IO_REPARSE_TAG_LX_CHR:
		fattr->cf_mode |= S_IFCHR | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_CHR;
		break;
	case IO_REPARSE_TAG_LX_BLK:
		fattr->cf_mode |= S_IFBLK | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_BLK;
		break;
	case 0: /* SMB1 symlink */
	case IO_REPARSE_TAG_SYMLINK:
	case IO_REPARSE_TAG_NFS:
		fattr->cf_mode = S_IFLNK;
		fattr->cf_dtype = DT_LNK;
		break;
	default:
		return false;
	}
	return true;
}

static void cifs_open_info_to_fattr(struct cifs_fattr *fattr,
				    struct cifs_open_info_data *data,
				    struct super_block *sb)
@@ -694,7 +731,6 @@ static void cifs_open_info_to_fattr(struct cifs_fattr *fattr,
	struct smb2_file_all_info *info = &data->fi;
	struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
	u32 reparse_tag = data->reparse_tag;

	memset(fattr, 0, sizeof(*fattr));
	fattr->cf_cifsattrs = le32_to_cpu(info->Attributes);
@@ -717,28 +753,13 @@ static void cifs_open_info_to_fattr(struct cifs_fattr *fattr,
	fattr->cf_eof = le64_to_cpu(info->EndOfFile);
	fattr->cf_bytes = le64_to_cpu(info->AllocationSize);
	fattr->cf_createtime = le64_to_cpu(info->CreationTime);

	fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
	if (reparse_tag == IO_REPARSE_TAG_LX_SYMLINK) {
		fattr->cf_mode |= S_IFLNK | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_LNK;
	} else if (reparse_tag == IO_REPARSE_TAG_LX_FIFO) {
		fattr->cf_mode |= S_IFIFO | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_FIFO;
	} else if (reparse_tag == IO_REPARSE_TAG_AF_UNIX) {
		fattr->cf_mode |= S_IFSOCK | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_SOCK;
	} else if (reparse_tag == IO_REPARSE_TAG_LX_CHR) {
		fattr->cf_mode |= S_IFCHR | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_CHR;
	} else if (reparse_tag == IO_REPARSE_TAG_LX_BLK) {
		fattr->cf_mode |= S_IFBLK | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_BLK;
	} else if (data->symlink || reparse_tag == IO_REPARSE_TAG_SYMLINK ||
		   reparse_tag == IO_REPARSE_TAG_NFS) {
		fattr->cf_mode = S_IFLNK;
		fattr->cf_dtype = DT_LNK;
	} else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {

	if (cifs_open_data_reparse(data) &&
	    cifs_reparse_point_to_fattr(cifs_sb, fattr, data->reparse_tag))
		goto out_reparse;

	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
		fattr->cf_mode = S_IFDIR | cifs_sb->ctx->dir_mode;
		fattr->cf_dtype = DT_DIR;
		/*
@@ -767,6 +788,7 @@ static void cifs_open_info_to_fattr(struct cifs_fattr *fattr,
		}
	}

out_reparse:
	if (S_ISLNK(fattr->cf_mode)) {
		fattr->cf_symlink_target = data->symlink_target;
		data->symlink_target = NULL;
@@ -957,6 +979,40 @@ static inline bool is_inode_cache_good(struct inode *ino)
	return ino && CIFS_CACHE_READ(CIFS_I(ino)) && CIFS_I(ino)->time != 0;
}

static int query_reparse(struct cifs_open_info_data *data,
			 struct super_block *sb,
			 const unsigned int xid,
			 struct cifs_tcon *tcon,
			 const char *full_path,
			 struct cifs_fattr *fattr)
{
	struct TCP_Server_Info *server = tcon->ses->server;
	struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
	bool reparse_point = data->reparse_point;
	u32 tag = data->reparse_tag;
	int rc = 0;

	if (!tag && server->ops->query_reparse_tag) {
		server->ops->query_reparse_tag(xid, tcon, cifs_sb,
					       full_path, &tag);
	}
	switch ((data->reparse_tag = tag)) {
	case 0: /* SMB1 symlink */
		reparse_point = false;
		fallthrough;
	case IO_REPARSE_TAG_NFS:
	case IO_REPARSE_TAG_SYMLINK:
		if (!data->symlink_target && server->ops->query_symlink) {
			rc = server->ops->query_symlink(xid, tcon,
							cifs_sb, full_path,
							&data->symlink_target,
							reparse_point);
		}
		break;
	}
	return rc;
}

int cifs_get_inode_info(struct inode **inode, const char *full_path,
			struct cifs_open_info_data *data, struct super_block *sb, int xid,
			const struct cifs_fid *fid)
@@ -1002,22 +1058,11 @@ int cifs_get_inode_info(struct inode **inode, const char *full_path,
		 * since we have to check if its reparse tag matches a known
		 * special file type e.g. symlink or fifo or char etc.
		 */
		if (data->reparse_point && data->symlink_target) {
			data->reparse_tag = IO_REPARSE_TAG_SYMLINK;
		} else if ((le32_to_cpu(data->fi.Attributes) & ATTR_REPARSE) &&
			   server->ops->query_reparse_tag) {
			tmprc = server->ops->query_reparse_tag(xid, tcon, cifs_sb, full_path,
							       &data->reparse_tag);
			cifs_dbg(FYI, "%s: query_reparse_tag: rc = %d\n", __func__, tmprc);
			if (server->ops->query_symlink) {
				tmprc = server->ops->query_symlink(xid, tcon, cifs_sb,
								   full_path,
								   &data->symlink_target,
								   data->reparse_point);
				cifs_dbg(FYI, "%s: query_symlink: rc = %d\n",
					 __func__, tmprc);
			}
		if (cifs_open_data_reparse(data)) {
			rc = query_reparse(data, sb, xid, tcon,
					   full_path, &fattr);
		}
		if (!rc)
			cifs_open_info_to_fattr(&fattr, data, sb);
		break;
	case -EREMOTE:
+6 −16
Original line number Diff line number Diff line
@@ -163,29 +163,19 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
	 * TODO: go through all documented  reparse tags to see if we can
	 * reasonably map some of them to directories vs. files vs. symlinks
	 */
	if ((fattr->cf_cifsattrs & ATTR_REPARSE) &&
	    cifs_reparse_point_to_fattr(cifs_sb, fattr, fattr->cf_cifstag))
		goto out_reparse;

	if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
		fattr->cf_mode = S_IFDIR | cifs_sb->ctx->dir_mode;
		fattr->cf_dtype = DT_DIR;
	} else if (fattr->cf_cifstag == IO_REPARSE_TAG_LX_SYMLINK) {
		fattr->cf_mode |= S_IFLNK | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_LNK;
	} else if (fattr->cf_cifstag == IO_REPARSE_TAG_LX_FIFO) {
		fattr->cf_mode |= S_IFIFO | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_FIFO;
	} else if (fattr->cf_cifstag == IO_REPARSE_TAG_AF_UNIX) {
		fattr->cf_mode |= S_IFSOCK | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_SOCK;
	} else if (fattr->cf_cifstag == IO_REPARSE_TAG_LX_CHR) {
		fattr->cf_mode |= S_IFCHR | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_CHR;
	} else if (fattr->cf_cifstag == IO_REPARSE_TAG_LX_BLK) {
		fattr->cf_mode |= S_IFBLK | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_BLK;
	} else { /* TODO: should we mark some other reparse points (like DFSR) as directories? */
	} else {
		fattr->cf_mode = S_IFREG | cifs_sb->ctx->file_mode;
		fattr->cf_dtype = DT_REG;
	}

out_reparse:
	/*
	 * 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
+78 −41
Original line number Diff line number Diff line
@@ -542,6 +542,33 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
	return rc;
}

static int parse_create_response(struct cifs_open_info_data *data,
				 struct cifs_sb_info *cifs_sb,
				 const struct kvec *iov)
{
	struct smb2_create_rsp *rsp = iov->iov_base;
	bool reparse_point = false;
	u32 tag = 0;
	int rc = 0;

	switch (rsp->hdr.Status) {
	case STATUS_STOPPED_ON_SYMLINK:
		rc = smb2_parse_symlink_response(cifs_sb, iov,
						 &data->symlink_target);
		if (rc)
			return rc;
		tag = IO_REPARSE_TAG_SYMLINK;
		reparse_point = true;
		break;
	case STATUS_SUCCESS:
		reparse_point = !!(rsp->Flags & SMB2_CREATE_FLAG_REPARSEPOINT);
		break;
	}
	data->reparse_point = reparse_point;
	data->reparse_tag = tag;
	return rc;
}

int smb2_query_path_info(const unsigned int xid,
			 struct cifs_tcon *tcon,
			 struct cifs_sb_info *cifs_sb,
@@ -551,6 +578,7 @@ int smb2_query_path_info(const unsigned int xid,
	__u32 create_options = 0;
	struct cifsFileInfo *cfile;
	struct cached_fid *cfid = NULL;
	struct smb2_hdr *hdr;
	struct kvec out_iov[3] = {};
	int out_buftype[3] = {};
	bool islink;
@@ -579,21 +607,22 @@ int smb2_query_path_info(const unsigned int xid,
	rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, FILE_READ_ATTRIBUTES, FILE_OPEN,
			      create_options, ACL_NO_MODE, data, SMB2_OP_QUERY_INFO, cfile,
			      NULL, NULL, out_iov, out_buftype);
	if (rc) {
		struct smb2_hdr *hdr = out_iov[0].iov_base;

		if (unlikely(!hdr || out_buftype[0] == CIFS_NO_BUFFER))
	hdr = out_iov[0].iov_base;
	/*
	 * If first iov is unset, then SMB session was dropped or we've got a
	 * cached open file (@cfile).
	 */
	if (!hdr || out_buftype[0] == CIFS_NO_BUFFER)
		goto out;
		if (rc == -EOPNOTSUPP && hdr->Command == SMB2_CREATE &&
		    hdr->Status == STATUS_STOPPED_ON_SYMLINK) {
			rc = smb2_parse_symlink_response(cifs_sb, out_iov,
							 &data->symlink_target);
			if (rc)

	switch (rc) {
	case 0:
	case -EOPNOTSUPP:
		rc = parse_create_response(data, cifs_sb, &out_iov[0]);
		if (rc || !data->reparse_point)
			goto out;

			data->reparse_point = true;
		create_options |= OPEN_REPARSE_POINT;

		/* Failed on a symbolic link - query a reparse point info */
		cifs_get_readable_path(tcon, full_path, &cfile);
		rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
@@ -601,8 +630,12 @@ int smb2_query_path_info(const unsigned int xid,
				      create_options, ACL_NO_MODE, data,
				      SMB2_OP_QUERY_INFO, cfile, NULL, NULL,
				      NULL, NULL);
			goto out;
		} else if (rc != -EREMOTE && hdr->Status == STATUS_OBJECT_NAME_INVALID) {
		break;
	case -EREMOTE:
		break;
	default:
		if (hdr->Status != STATUS_OBJECT_NAME_INVALID)
			break;
		rc2 = cifs_inval_name_dfs_link_error(xid, tcon, cifs_sb,
						     full_path, &islink);
		if (rc2) {
@@ -612,7 +645,6 @@ int smb2_query_path_info(const unsigned int xid,
		if (islink)
			rc = -EREMOTE;
	}
	}

out:
	free_rsp_buf(out_buftype[0], out_iov[0].iov_base);
@@ -653,26 +685,32 @@ int smb311_posix_query_path_info(const unsigned int xid,
	rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, FILE_READ_ATTRIBUTES, FILE_OPEN,
			      create_options, ACL_NO_MODE, data, SMB2_OP_POSIX_QUERY_INFO, cfile,
			      &sidsbuf, &sidsbuflen, out_iov, out_buftype);
	if (rc == -EOPNOTSUPP) {
	/*
	 * If first iov is unset, then SMB session was dropped or we've got a
	 * cached open file (@cfile).
	 */
	if (!out_iov[0].iov_base || out_buftype[0] == CIFS_NO_BUFFER)
		goto out;

	switch (rc) {
	case 0:
	case -EOPNOTSUPP:
		/* BB TODO: When support for special files added to Samba re-verify this path */
		if (out_iov[0].iov_base && out_buftype[0] != CIFS_NO_BUFFER &&
		    ((struct smb2_hdr *)out_iov[0].iov_base)->Command == SMB2_CREATE &&
		    ((struct smb2_hdr *)out_iov[0].iov_base)->Status == STATUS_STOPPED_ON_SYMLINK) {
			rc = smb2_parse_symlink_response(cifs_sb, out_iov, &data->symlink_target);
			if (rc)
		rc = parse_create_response(data, cifs_sb, &out_iov[0]);
		if (rc || !data->reparse_point)
			goto out;
		}
		data->reparse_point = true;
		create_options |= OPEN_REPARSE_POINT;

		create_options |= OPEN_REPARSE_POINT;
		/* Failed on a symbolic link - query a reparse point info */
		cifs_get_readable_path(tcon, full_path, &cfile);
		rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, FILE_READ_ATTRIBUTES,
				      FILE_OPEN, create_options, ACL_NO_MODE, data,
				      SMB2_OP_POSIX_QUERY_INFO, cfile,
				      &sidsbuf, &sidsbuflen, NULL, NULL);
		break;
	}

out:
	if (rc == 0) {
		sidsbuf_end = sidsbuf + sidsbuflen;

@@ -692,7 +730,6 @@ int smb311_posix_query_path_info(const unsigned int xid,
		memcpy(group, sidsbuf + owner_len, group_len);
	}

out:
	kfree(sidsbuf);
	free_rsp_buf(out_buftype[0], out_iov[0].iov_base);
	free_rsp_buf(out_buftype[1], out_iov[1].iov_base);