From 2e4564b31b645f599d531e2c8bd0e47316e02223 Mon Sep 17 00:00:00 2001 From: Steve French Date: Thu, 22 Oct 2020 22:03:14 -0500 Subject: [PATCH] smb3: add support for stat of WSL reparse points for special file types MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This is needed so when mounting to Windows we do not misinterpret various special files created by Linux (WSL) as symlinks. An earlier patch addressed readdir. This patch fixes stat (getattr). With this patch:   File: /mnt1/char   Size: 0          Blocks: 0          IO Block: 16384  character special file Device: 34h/52d Inode: 844424930132069  Links: 1     Device type: 0,0 Access: (0755/crwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root) Access: 2020-10-21 17:46:51.839458900 -0500 Modify: 2020-10-21 17:46:51.839458900 -0500 Change: 2020-10-21 18:30:39.797358800 -0500  Birth: -   File: /mnt1/fifo   Size: 0          Blocks: 0          IO Block: 16384  fifo Device: 34h/52d Inode: 1125899906842722  Links: 1 Access: (0755/prwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root) Access: 2020-10-21 16:21:37.259249700 -0500 Modify: 2020-10-21 16:21:37.259249700 -0500 Change: 2020-10-21 18:30:39.797358800 -0500  Birth: -   File: /mnt1/block   Size: 0          Blocks: 0          IO Block: 16384  block special file Device: 34h/52d Inode: 844424930132068  Links: 1     Device type: 0,0 Access: (0755/brwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root) Access: 2020-10-21 17:10:47.913103200 -0500 Modify: 2020-10-21 17:10:47.913103200 -0500 Change: 2020-10-21 18:30:39.796725500 -0500  Birth: - without the patch all show up incorrectly as symlinks with annoying "operation not supported error also returned"   File: /mnt1/charstat: cannot read symbolic link '/mnt1/char': Operation not supported   Size: 0          Blocks: 0          IO Block: 16384  symbolic link Device: 34h/52d Inode: 844424930132069  Links: 1 Access: (0000/l---------)  Uid: (    0/    root)   Gid: (    0/    root) Access: 2020-10-21 17:46:51.839458900 -0500 Modify: 2020-10-21 17:46:51.839458900 -0500 Change: 2020-10-21 18:30:39.797358800 -0500  Birth: -   File: /mnt1/fifostat: cannot read symbolic link '/mnt1/fifo': Operation not supported   Size: 0          Blocks: 0          IO Block: 16384  symbolic link Device: 34h/52d Inode: 1125899906842722  Links: 1 Access: (0000/l---------)  Uid: (    0/    root)   Gid: (    0/    root) Access: 2020-10-21 16:21:37.259249700 -0500 Modify: 2020-10-21 16:21:37.259249700 -0500 Change: 2020-10-21 18:30:39.797358800 -0500  Birth: -   File: /mnt1/blockstat: cannot read symbolic link '/mnt1/block': Operation not supported   Size: 0          Blocks: 0          IO Block: 16384  symbolic link Device: 34h/52d Inode: 844424930132068  Links: 1 Access: (0000/l---------)  Uid: (    0/    root)   Gid: (    0/    root) Access: 2020-10-21 17:10:47.913103200 -0500 Modify: 2020-10-21 17:10:47.913103200 -0500 Change: 2020-10-21 18:30:39.796725500 -0500 Signed-off-by: Steve French Reviewed-by: Ronnie Sahlberg --- fs/cifs/cifsglob.h | 4 ++ fs/cifs/inode.c | 44 +++++++++++++---- fs/cifs/smb2inode.c | 12 ++--- fs/cifs/smb2ops.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/cifs/smb2pdu.h | 5 ++ fs/cifs/smb2proto.h | 3 ++ 6 files changed, 189 insertions(+), 14 deletions(-) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index b6925ae..484ec2d 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -298,6 +298,10 @@ struct smb_version_operations { /* query file data from the server */ int (*query_file_info)(const unsigned int, struct cifs_tcon *, struct cifs_fid *, FILE_ALL_INFO *); + /* query reparse tag from srv to determine which type of special file */ + int (*query_reparse_tag)(const unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, const char *path, + __u32 *reparse_tag); /* get server index number */ int (*get_srv_inum)(const unsigned int, struct cifs_tcon *, struct cifs_sb_info *, const char *, diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index daec31b..9ee5f30 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -656,7 +656,7 @@ smb311_posix_info_to_fattr(struct cifs_fattr *fattr, struct smb311_posix_qinfo * static void cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info, struct super_block *sb, bool adjust_tz, - bool symlink) + bool symlink, u32 reparse_tag) { struct cifs_sb_info *cifs_sb = CIFS_SB(sb); struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); @@ -684,8 +684,22 @@ 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 (symlink) { + if (reparse_tag == IO_REPARSE_TAG_LX_SYMLINK) { + fattr->cf_mode |= S_IFLNK | cifs_sb->mnt_file_mode; + fattr->cf_dtype = DT_LNK; + } else if (reparse_tag == IO_REPARSE_TAG_LX_FIFO) { + fattr->cf_mode |= S_IFIFO | cifs_sb->mnt_file_mode; + fattr->cf_dtype = DT_FIFO; + } else if (reparse_tag == IO_REPARSE_TAG_AF_UNIX) { + fattr->cf_mode |= S_IFSOCK | cifs_sb->mnt_file_mode; + fattr->cf_dtype = DT_SOCK; + } else if (reparse_tag == IO_REPARSE_TAG_LX_CHR) { + fattr->cf_mode |= S_IFCHR | cifs_sb->mnt_file_mode; + fattr->cf_dtype = DT_CHR; + } else if (reparse_tag == IO_REPARSE_TAG_LX_BLK) { + fattr->cf_mode |= S_IFBLK | cifs_sb->mnt_file_mode; + fattr->cf_dtype = DT_BLK; + } else if (symlink) { /* TODO add more reparse tag checks */ fattr->cf_mode = S_IFLNK; fattr->cf_dtype = DT_LNK; } else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) { @@ -740,8 +754,9 @@ cifs_get_file_info(struct file *filp) rc = server->ops->query_file_info(xid, tcon, &cfile->fid, &find_data); switch (rc) { case 0: + /* TODO: add support to query reparse tag */ cifs_all_info_to_fattr(&fattr, &find_data, inode->i_sb, false, - false); + false, 0 /* no reparse tag */); break; case -EREMOTE: cifs_create_dfs_fattr(&fattr, inode->i_sb); @@ -910,12 +925,13 @@ cifs_get_inode_info(struct inode **inode, struct cifs_sb_info *cifs_sb = CIFS_SB(sb); bool adjust_tz = false; struct cifs_fattr fattr = {0}; - bool symlink = false; + bool is_reparse_point = false; FILE_ALL_INFO *data = in_data; FILE_ALL_INFO *tmp_data = NULL; void *smb1_backup_rsp_buf = NULL; int rc = 0; int tmprc = 0; + __u32 reparse_tag = 0; tlink = cifs_sb_tlink(cifs_sb); if (IS_ERR(tlink)) @@ -938,8 +954,8 @@ cifs_get_inode_info(struct inode **inode, goto out; } rc = server->ops->query_path_info(xid, tcon, cifs_sb, - full_path, tmp_data, - &adjust_tz, &symlink); + full_path, tmp_data, + &adjust_tz, &is_reparse_point); data = tmp_data; } @@ -949,7 +965,19 @@ cifs_get_inode_info(struct inode **inode, switch (rc) { case 0: - cifs_all_info_to_fattr(&fattr, data, sb, adjust_tz, symlink); + /* + * If the file is a reparse point, it is more complicated + * since we have to check if its reparse tag matches a known + * special file type e.g. symlink or fifo or char etc. + */ + if ((le32_to_cpu(data->Attributes) & ATTR_REPARSE) && + server->ops->query_reparse_tag) { + rc = server->ops->query_reparse_tag(xid, tcon, cifs_sb, + full_path, &reparse_tag); + cifs_dbg(FYI, "reparse tag 0x%x\n", reparse_tag); + } + cifs_all_info_to_fattr(&fattr, data, sb, adjust_tz, + is_reparse_point, reparse_tag); break; case -EREMOTE: /* DFS link, no metadata available on this server */ diff --git a/fs/cifs/smb2inode.c b/fs/cifs/smb2inode.c index df6212e..1f900b8 100644 --- a/fs/cifs/smb2inode.c +++ b/fs/cifs/smb2inode.c @@ -506,7 +506,7 @@ 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, bool *symlink) + FILE_ALL_INFO *data, bool *adjust_tz, bool *reparse) { int rc; struct smb2_file_all_info *smb2_data; @@ -516,7 +516,7 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, struct cached_fid *cfid = NULL; *adjust_tz = false; - *symlink = false; + *reparse = false; smb2_data = kzalloc(sizeof(struct smb2_file_all_info) + PATH_MAX * 2, GFP_KERNEL); @@ -548,7 +548,7 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, FILE_READ_ATTRIBUTES, FILE_OPEN, create_options, ACL_NO_MODE, smb2_data, SMB2_OP_QUERY_INFO, cfile); if (rc == -EOPNOTSUPP) { - *symlink = true; + *reparse = true; create_options |= OPEN_REPARSE_POINT; /* Failed on a symbolic link - query a reparse point info */ @@ -570,7 +570,7 @@ out: int smb311_posix_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const char *full_path, - struct smb311_posix_qinfo *data, bool *adjust_tz, bool *symlink) + struct smb311_posix_qinfo *data, bool *adjust_tz, bool *reparse) { int rc; __u32 create_options = 0; @@ -578,7 +578,7 @@ smb311_posix_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, struct smb311_posix_qinfo *smb2_data; *adjust_tz = false; - *symlink = false; + *reparse = false; /* BB TODO: Make struct larger when add support for parsing owner SIDs */ smb2_data = kzalloc(sizeof(struct smb311_posix_qinfo), @@ -599,7 +599,7 @@ smb311_posix_query_path_info(const unsigned int xid, struct cifs_tcon *tcon, ACL_NO_MODE, smb2_data, SMB2_OP_POSIX_QUERY_INFO, cfile); if (rc == -EOPNOTSUPP) { /* BB TODO: When support for special files added to Samba re-verify this path */ - *symlink = true; + *reparse = true; create_options |= OPEN_REPARSE_POINT; /* Failed on a symbolic link - query a reparse point info */ diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index 3cde719..0fe87f3 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -3034,6 +3034,138 @@ smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon, return rc; } +int +smb2_query_reparse_tag(const unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, const char *full_path, + __u32 *tag) +{ + int rc; + __le16 *utf16_path = NULL; + __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; + struct cifs_open_parms oparms; + struct cifs_fid fid; + struct kvec err_iov = {NULL, 0}; + struct TCP_Server_Info *server = cifs_pick_channel(tcon->ses); + int flags = 0; + struct smb_rqst rqst[3]; + int resp_buftype[3]; + struct kvec rsp_iov[3]; + struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; + struct kvec io_iov[SMB2_IOCTL_IOV_SIZE]; + struct kvec close_iov[1]; + struct smb2_create_rsp *create_rsp; + struct smb2_ioctl_rsp *ioctl_rsp; + struct reparse_data_buffer *reparse_buf; + u32 plen; + + cifs_dbg(FYI, "%s: path: %s\n", __func__, full_path); + + if (smb3_encryption_required(tcon)) + flags |= CIFS_TRANSFORM_REQ; + + memset(rqst, 0, sizeof(rqst)); + resp_buftype[0] = resp_buftype[1] = resp_buftype[2] = CIFS_NO_BUFFER; + memset(rsp_iov, 0, sizeof(rsp_iov)); + + utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb); + if (!utf16_path) + return -ENOMEM; + + /* + * setup smb2open - TODO add optimization to call cifs_get_readable_path + * to see if there is a handle already open that we can use + */ + memset(&open_iov, 0, sizeof(open_iov)); + rqst[0].rq_iov = open_iov; + rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; + + memset(&oparms, 0, sizeof(oparms)); + oparms.tcon = tcon; + oparms.desired_access = FILE_READ_ATTRIBUTES; + oparms.disposition = FILE_OPEN; + oparms.create_options = cifs_create_options(cifs_sb, OPEN_REPARSE_POINT); + oparms.fid = &fid; + oparms.reconnect = false; + + rc = SMB2_open_init(tcon, server, + &rqst[0], &oplock, &oparms, utf16_path); + if (rc) + goto query_rp_exit; + smb2_set_next_command(tcon, &rqst[0]); + + + /* IOCTL */ + memset(&io_iov, 0, sizeof(io_iov)); + rqst[1].rq_iov = io_iov; + rqst[1].rq_nvec = SMB2_IOCTL_IOV_SIZE; + + rc = SMB2_ioctl_init(tcon, server, + &rqst[1], fid.persistent_fid, + fid.volatile_fid, FSCTL_GET_REPARSE_POINT, + true /* is_fctl */, NULL, 0, + CIFSMaxBufSize - + MAX_SMB2_CREATE_RESPONSE_SIZE - + MAX_SMB2_CLOSE_RESPONSE_SIZE); + if (rc) + goto query_rp_exit; + + smb2_set_next_command(tcon, &rqst[1]); + smb2_set_related(&rqst[1]); + + + /* Close */ + memset(&close_iov, 0, sizeof(close_iov)); + rqst[2].rq_iov = close_iov; + rqst[2].rq_nvec = 1; + + rc = SMB2_close_init(tcon, server, + &rqst[2], COMPOUND_FID, COMPOUND_FID, false); + if (rc) + goto query_rp_exit; + + smb2_set_related(&rqst[2]); + + rc = compound_send_recv(xid, tcon->ses, server, + flags, 3, rqst, + resp_buftype, rsp_iov); + + create_rsp = rsp_iov[0].iov_base; + if (create_rsp && create_rsp->sync_hdr.Status) + err_iov = rsp_iov[0]; + ioctl_rsp = rsp_iov[1].iov_base; + + /* + * Open was successful and we got an ioctl response. + */ + if (rc == 0) { + /* See MS-FSCC 2.3.23 */ + + reparse_buf = (struct reparse_data_buffer *) + ((char *)ioctl_rsp + + le32_to_cpu(ioctl_rsp->OutputOffset)); + plen = le32_to_cpu(ioctl_rsp->OutputCount); + + if (plen + le32_to_cpu(ioctl_rsp->OutputOffset) > + rsp_iov[1].iov_len) { + cifs_tcon_dbg(FYI, "srv returned invalid ioctl len: %d\n", + plen); + rc = -EIO; + goto query_rp_exit; + } + *tag = le32_to_cpu(reparse_buf->ReparseTag); + } + + query_rp_exit: + kfree(utf16_path); + SMB2_open_free(&rqst[0]); + SMB2_ioctl_free(&rqst[1]); + SMB2_close_free(&rqst[2]); + free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); + free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); + free_rsp_buf(resp_buftype[2], rsp_iov[2].iov_base); + return rc; +} + static struct cifs_ntsd * get_smb2_acl_by_fid(struct cifs_sb_info *cifs_sb, const struct cifs_fid *cifsfid, u32 *pacllen) @@ -4986,6 +5118,8 @@ struct smb_version_operations smb30_operations = { .can_echo = smb2_can_echo, .echo = SMB2_echo, .query_path_info = smb2_query_path_info, + /* WSL tags introduced long after smb2.1, enable for SMB3, 3.11 only */ + .query_reparse_tag = smb2_query_reparse_tag, .get_srv_inum = smb2_get_srv_inum, .query_file_info = smb2_query_file_info, .set_path_size = smb2_set_path_size, @@ -5097,6 +5231,7 @@ struct smb_version_operations smb311_operations = { .can_echo = smb2_can_echo, .echo = SMB2_echo, .query_path_info = smb2_query_path_info, + .query_reparse_tag = smb2_query_reparse_tag, .get_srv_inum = smb2_get_srv_inum, .query_file_info = smb2_query_file_info, .set_path_size = smb2_set_path_size, diff --git a/fs/cifs/smb2pdu.h b/fs/cifs/smb2pdu.h index 171f549..14244d2 100644 --- a/fs/cifs/smb2pdu.h +++ b/fs/cifs/smb2pdu.h @@ -1691,6 +1691,11 @@ struct smb2_file_eof_info { /* encoding of request for level 10 */ __le64 EndOfFile; /* new end of file value */ } __packed; /* level 20 Set */ +struct smb2_file_reparse_point_info { + __le64 IndexNumber; + __le32 Tag; +} __packed; + struct smb2_file_network_open_info { __le64 CreationTime; __le64 LastAccessTime; diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h index 67c50d7..d411044 100644 --- a/fs/cifs/smb2proto.h +++ b/fs/cifs/smb2proto.h @@ -77,6 +77,9 @@ extern void close_shroot_lease(struct cached_fid *cfid); extern void close_shroot_lease_locked(struct cached_fid *cfid); extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst, struct smb2_file_all_info *src); +extern int smb2_query_reparse_tag(const unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, const char *path, + __u32 *reparse_tag); 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, -- 2.7.4