diff mbox series

[5/7] smb: client: allow creating special files via reparse points

Message ID 20231121231258.29562-5-pc@manguebit.com
State New
Headers show
Series [1/7] smb: client: implement ->query_reparse_point() for SMB1 | expand

Commit Message

Paulo Alcantara Nov. 21, 2023, 11:12 p.m. UTC
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(-)

Comments

Paulo Alcantara Nov. 22, 2023, 3:16 p.m. UTC | #1
Paulo Alcantara <pc@manguebit.com> writes:

> +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));

Err, forgot having @buf->Reserved set to 0 here, sorry.  MS-FSCC
doesnt't say we _must_ to, however it's better not sending garbage over
the wire.

Let me know if you'd need v2 for the above.
diff mbox series

Patch

diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index 47f49be69ced..88b7cf23348c 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -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,
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index c94940af5d4b..c09da386a36b 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -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)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 82ab62fd0040..bc069deb5031 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -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
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index 46eff9ec302a..c98cda998de2 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -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 */