Skip to content

Commit

Permalink
smb: client: parse reparse point flag in create response
Browse files Browse the repository at this point in the history
Check for reparse point flag on query info calls as specified in
MS-SMB2 2.2.14.

Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
  • Loading branch information
Paulo Alcantara authored and Steve French committed Aug 20, 2023
1 parent 348a04a commit 5f71ebc
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 95 deletions.
4 changes: 4 additions & 0 deletions fs/smb/client/cifsglob.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions fs/smb/client/cifsproto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
121 changes: 83 additions & 38 deletions fs/smb/client/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -687,14 +687,50 @@ 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)
{
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);
Expand All @@ -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;
/*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1002,23 +1058,12 @@ 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);
}
cifs_open_info_to_fattr(&fattr, data, sb);
if (!rc)
cifs_open_info_to_fattr(&fattr, data, sb);
break;
case -EREMOTE:
/* DFS link, no metadata available on this server */
Expand Down
22 changes: 6 additions & 16 deletions fs/smb/client/readdir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
119 changes: 78 additions & 41 deletions fs/smb/client/smb2inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -579,39 +607,43 @@ 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;

switch (rc) {
case 0:
case -EOPNOTSUPP:
rc = parse_create_response(data, cifs_sb, &out_iov[0]);
if (rc || !data->reparse_point)
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)
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,
FILE_READ_ATTRIBUTES, FILE_OPEN,
create_options, ACL_NO_MODE, data,
SMB2_OP_QUERY_INFO, cfile, NULL, NULL,
NULL, NULL);

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_QUERY_INFO, cfile, NULL, NULL,
NULL, NULL);
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) {
rc = rc2;
goto out;
} else if (rc != -EREMOTE && hdr->Status == STATUS_OBJECT_NAME_INVALID) {
rc2 = cifs_inval_name_dfs_link_error(xid, tcon, cifs_sb,
full_path, &islink);
if (rc2) {
rc = rc2;
goto out;
}
if (islink)
rc = -EREMOTE;
}
if (islink)
rc = -EREMOTE;
}

out:
Expand Down Expand Up @@ -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)
goto out;
}
data->reparse_point = true;
create_options |= OPEN_REPARSE_POINT;
rc = parse_create_response(data, cifs_sb, &out_iov[0]);
if (rc || !data->reparse_point)
goto out;

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;

Expand All @@ -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);
Expand Down

0 comments on commit 5f71ebc

Please sign in to comment.