@@ -1090,7 +1090,8 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
rc = 0;
goto out;
default:
- if (data->symlink_target) {
+ /* Check for cached reparse point data */
+ if (data->symlink_target || data->reparse.buf) {
rc = 0;
} else if (server->ops->parse_reparse_point) {
rc = server->ops->parse_reparse_point(cifs_sb,
@@ -529,6 +529,38 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}
+struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
+ struct super_block *sb,
+ const unsigned int xid,
+ struct cifs_tcon *tcon,
+ const char *full_path)
+{
+ struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
+ struct cifsFileInfo *cfile;
+ struct inode *new = NULL;
+ int rc;
+
+ if (tcon->posix_extensions) {
+ rc = smb311_posix_get_inode_info(&new, full_path, sb, xid);
+ } else {
+ /*
+ * Since we know it is a reparse point already, query info it
+ * directly and provide cached file metadata to
+ * cifs_get_inode_info() in order to avoid extra roundtrips.
+ */
+ cifs_get_readable_path(tcon, full_path, &cfile);
+ rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
+ FILE_READ_ATTRIBUTES, FILE_OPEN,
+ OPEN_REPARSE_POINT, ACL_NO_MODE, data,
+ SMB2_OP_QUERY_INFO, cfile, NULL, NULL,
+ NULL, NULL);
+ if (rc)
+ return ERR_PTR(rc);
+ rc = cifs_get_inode_info(&new, full_path, data, sb, xid, NULL);
+ }
+ return rc ? ERR_PTR(rc) : new;
+}
+
static int parse_create_response(struct cifs_open_info_data *data,
struct cifs_sb_info *cifs_sb,
const struct kvec *iov)
@@ -3114,6 +3114,97 @@ static int smb2_query_reparse_point(const unsigned int xid,
return rc;
}
+int smb2_create_reparse_point(const unsigned int xid,
+ struct cifs_tcon *tcon,
+ struct cifs_sb_info *cifs_sb,
+ const char *full_path,
+ void *rbuf, u16 rsize)
+{
+ struct smb2_compound_vars *vars;
+ struct cifs_open_parms oparms;
+ struct TCP_Server_Info *server = cifs_pick_channel(tcon->ses);
+ struct smb_rqst *rqst;
+ struct cifs_fid fid;
+ struct kvec *rsp_iov;
+ __le16 *utf16_path = NULL;
+ __u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
+ int flags = CIFS_CP_CREATE_CLOSE_OP;
+ int resp_buftype[3];
+ int rc;
+
+ utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb);
+ if (!utf16_path)
+ return -ENOMEM;
+
+ resp_buftype[0] = resp_buftype[1] = resp_buftype[2] = CIFS_NO_BUFFER;
+ vars = kzalloc(sizeof(*vars), GFP_KERNEL);
+ if (!vars) {
+ kfree(utf16_path);
+ return -ENOMEM;
+ }
+ rqst = vars->rqst;
+ rsp_iov = vars->rsp_iov;
+
+ rqst[0].rq_iov = vars->open_iov;
+ rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE;
+
+ oparms = (struct cifs_open_parms) {
+ .tcon = tcon,
+ .cifs_sb = cifs_sb,
+ .desired_access = SYNCHRONIZE | DELETE |
+ FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
+ .create_options = cifs_create_options(cifs_sb, CREATE_NOT_DIR |
+ OPEN_REPARSE_POINT),
+ .disposition = FILE_CREATE,
+ .path = full_path,
+ .fid = &fid,
+ };
+
+ rc = SMB2_open_init(tcon, server, &rqst[0],
+ &oplock, &oparms, utf16_path);
+ if (rc)
+ goto out;
+ smb2_set_next_command(tcon, &rqst[0]);
+
+ rqst[1].rq_iov = vars->io_iov;
+ rqst[1].rq_nvec = SMB2_IOCTL_IOV_SIZE;
+
+ rc = SMB2_ioctl_init(tcon, server, &rqst[1],
+ COMPOUND_FID, COMPOUND_FID,
+ FSCTL_SET_REPARSE_POINT,
+ rbuf, rsize, 0);
+ if (rc)
+ goto out;
+ smb2_set_next_command(tcon, &rqst[1]);
+ smb2_set_related(&rqst[1]);
+
+ rqst[2].rq_iov = &vars->close_iov;
+ rqst[2].rq_nvec = 1;
+
+ rc = SMB2_close_init(tcon, server, &rqst[2],
+ COMPOUND_FID, COMPOUND_FID, false);
+ if (rc)
+ goto out;
+ smb2_set_related(&rqst[2]);
+
+ if (smb3_encryption_required(tcon))
+ flags |= CIFS_TRANSFORM_REQ;
+
+ rc = compound_send_recv(xid, tcon->ses, server, flags,
+ 3, rqst, resp_buftype, rsp_iov);
+
+out:
+ 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);
+ kfree(utf16_path);
+ kfree(vars);
+ return rc;
+}
+
static struct cifs_ntsd *
get_smb2_acl_by_fid(struct cifs_sb_info *cifs_sb,
const struct cifs_fid *cifsfid, u32 *pacllen, u32 info)
@@ -5133,11 +5224,88 @@ int cifs_sfu_make_node(unsigned int xid, struct inode *inode,
return rc;
}
+static inline u64 mode_nfs_type(mode_t mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFBLK: return NFS_SPECFILE_BLK;
+ case S_IFCHR: return NFS_SPECFILE_CHR;
+ case S_IFIFO: return NFS_SPECFILE_FIFO;
+ case S_IFSOCK: return NFS_SPECFILE_SOCK;
+ }
+ return 0;
+}
+
+static int create_nfs_reparse(const unsigned int xid,
+ struct cifs_tcon *tcon,
+ struct cifs_sb_info *cifs_sb,
+ const char *full_path, mode_t mode,
+ struct reparse_posix_data *buf)
+{
+ u64 type;
+ u16 len, dlen;
+
+ len = sizeof(*buf);
+
+ switch ((type = mode_nfs_type(mode))) {
+ case NFS_SPECFILE_BLK:
+ case NFS_SPECFILE_CHR:
+ dlen = sizeof(__le64);
+ break;
+ case NFS_SPECFILE_FIFO:
+ case NFS_SPECFILE_SOCK:
+ dlen = 0;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ buf->ReparseTag = cpu_to_le32(IO_REPARSE_TAG_NFS);
+ buf->InodeType = cpu_to_le64(type);
+ buf->ReparseDataLength = cpu_to_le16(len + dlen -
+ sizeof(struct reparse_data_buffer));
+
+ return smb2_create_reparse_point(xid, tcon, cifs_sb,
+ full_path, buf, len + dlen);
+}
+
+static int nfs_make_node(unsigned int xid, struct inode *inode,
+ struct dentry *dentry, struct cifs_tcon *tcon,
+ const char *full_path, umode_t mode, dev_t dev)
+{
+ struct cifs_open_info_data data;
+ struct reparse_posix_data *p;
+ struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+ struct inode *new;
+ __u8 buf[sizeof(*p) + sizeof(__le64)];
+ int rc;
+
+ p = (struct reparse_posix_data *)buf;
+ *(__le64 *)p->DataBuffer = cpu_to_le64(((u64)MAJOR(dev) << 32) |
+ MINOR(dev));
+
+ rc = create_nfs_reparse(xid, tcon, cifs_sb, full_path, mode, p);
+ if (rc)
+ return rc;
+
+ data = (struct cifs_open_info_data) {
+ .reparse_point = true,
+ .reparse = { .tag = IO_REPARSE_TAG_NFS, .posix = p, },
+ };
+
+ new = smb2_get_reparse_inode(&data, inode->i_sb, xid, tcon, full_path);
+ if (!IS_ERR(new))
+ d_instantiate(dentry, new);
+ else
+ rc = PTR_ERR(new);
+ return rc;
+}
+
static int smb2_make_node(unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, umode_t mode, dev_t dev)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+ int rc;
/*
* Check if mounted with mount parm 'sfu' mount parm.
@@ -5145,15 +5313,14 @@ static int smb2_make_node(unsigned int xid, struct inode *inode,
* supports block and char device (no socket & fifo),
* and was used by default in earlier versions of Windows
*/
- if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL))
- return -EPERM;
- /*
- * TODO: Add ability to create instead via reparse point. Windows (e.g.
- * their current NFS server) uses this approach to expose special files
- * over SMB2/SMB3 and Samba will do this with SMB3.1.1 POSIX Extensions
- */
- return cifs_sfu_make_node(xid, inode, dentry, tcon,
- full_path, mode, dev);
+ if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL) {
+ rc = cifs_sfu_make_node(xid, inode, dentry, tcon,
+ full_path, mode, dev);
+ } else {
+ rc = nfs_make_node(xid, inode, dentry, tcon,
+ full_path, mode, dev);
+ }
+ return rc;
}
#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
@@ -56,6 +56,11 @@ extern int smb3_handle_read_data(struct TCP_Server_Info *server,
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);
+struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
+ struct super_block *sb,
+ const unsigned int xid,
+ struct cifs_tcon *tcon,
+ const char *full_path);
int smb2_query_path_info(const unsigned int xid,
struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb,
@@ -287,4 +292,9 @@ int smb311_posix_query_path_info(const unsigned int xid,
int posix_info_parse(const void *beg, const void *end,
struct smb2_posix_info_parsed *out);
int posix_info_sid_size(const void *beg, const void *end);
+int smb2_create_reparse_point(const unsigned int xid,
+ struct cifs_tcon *tcon,
+ struct cifs_sb_info *cifs_sb,
+ const char *full_path,
+ void *rbuf, u16 rsize);
#endif /* _SMB2PROTO_H */
Add support for creating special files (e.g. char/block devices, sockets, fifos) via NFS reparse points on SMB2+, which are fully supported by most SMB servers and documented in MS-FSCC. Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com> --- fs/smb/client/inode.c | 3 +- fs/smb/client/smb2inode.c | 32 +++++++ fs/smb/client/smb2ops.c | 185 ++++++++++++++++++++++++++++++++++++-- fs/smb/client/smb2proto.h | 10 +++ 4 files changed, 220 insertions(+), 10 deletions(-)