]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
cifs: Add support for creating reparse points over SMB1
authorPali Rohár <pali@kernel.org>
Wed, 25 Dec 2024 23:43:22 +0000 (00:43 +0100)
committerSteve French <stfrench@microsoft.com>
Sun, 27 Jul 2025 22:43:08 +0000 (17:43 -0500)
SMB1 already supports querying reparse points and detecting types of
symlink, fifo, socket, block and char.

This change implements the missing part - ability to create a new reparse
points over SMB1. This includes everything which SMB2+ already supports:
- native SMB symlinks and sockets
- NFS style of special files (symlinks, fifos, sockets, char/block devs)
- WSL style of special files (symlinks, fifos, sockets, char/block devs)

Attaching a reparse point to an existing file or directory is done via
SMB1 SMB_COM_NT_TRANSACT/NT_TRANSACT_IOCTL/FSCTL_SET_REPARSE_POINT command
and implemented in a new cifs_create_reparse_inode() function.

This change introduce a new callback ->create_reparse_inode() which creates
a new reperse point file or directory and returns inode. For SMB1 it is
provided via that new cifs_create_reparse_inode() function.

Existing reparse.c code was only slightly updated to call new protocol
callback ->create_reparse_inode() instead of hardcoded SMB2+ function.
This make the whole reparse.c code to work with every SMB dialect.

The original callback ->create_reparse_symlink() is not needed anymore as
the implementation of new create_reparse_symlink() function is dialect
agnostic too. So the link.c code was updated to call that function directly
(and not via callback).

Signed-off-by: Pali Rohár <pali@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsglob.h
fs/smb/client/cifsproto.h
fs/smb/client/cifssmb.c
fs/smb/client/link.c
fs/smb/client/reparse.c
fs/smb/client/reparse.h
fs/smb/client/smb1ops.c
fs/smb/client/smb2inode.c
fs/smb/client/smb2ops.c
fs/smb/client/smb2proto.h

index 89160bc34d35397166c1ce79dc9c4e1292736011..19dd901fe8abf9fc5aea2c1cab8ac9c33f19e51c 100644 (file)
@@ -627,12 +627,14 @@ struct smb_version_operations {
        bool (*is_network_name_deleted)(char *buf, struct TCP_Server_Info *srv);
        struct reparse_data_buffer * (*get_reparse_point_buffer)(const struct kvec *rsp_iov,
                                                                 u32 *plen);
-       int (*create_reparse_symlink)(const unsigned int xid,
-                                     struct inode *inode,
-                                     struct dentry *dentry,
-                                     struct cifs_tcon *tcon,
-                                     const char *full_path,
-                                     const char *symname);
+       struct inode * (*create_reparse_inode)(struct cifs_open_info_data *data,
+                                              struct super_block *sb,
+                                              const unsigned int xid,
+                                              struct cifs_tcon *tcon,
+                                              const char *full_path,
+                                              bool directory,
+                                              struct kvec *reparse_iov,
+                                              struct kvec *xattr_iov);
 };
 
 struct smb_version_values {
index 045227ed4efc96c9b8d84550c7b2bebae1386dc6..40ec0634377f697620bbee7b0e0e56b6ca5cbd32 100644 (file)
@@ -483,6 +483,14 @@ extern int cifs_query_reparse_point(const unsigned int xid,
                                    const char *full_path,
                                    u32 *tag, struct kvec *rsp,
                                    int *rsp_buftype);
+extern struct inode *cifs_create_reparse_inode(struct cifs_open_info_data *data,
+                                              struct super_block *sb,
+                                              const unsigned int xid,
+                                              struct cifs_tcon *tcon,
+                                              const char *full_path,
+                                              bool directory,
+                                              struct kvec *reparse_iov,
+                                              struct kvec *xattr_iov);
 extern int CIFSSMB_set_compression(const unsigned int xid,
                                   struct cifs_tcon *tcon, __u16 fid);
 extern int CIFS_open(const unsigned int xid, struct cifs_open_parms *oparms,
index 9e337ee10c8a8cbc623790cd3597d21ec4625168..6c890db0659385d3d408b647db2268bfc4ab19db 100644 (file)
@@ -2851,6 +2851,134 @@ error:
        return rc;
 }
 
+struct inode *cifs_create_reparse_inode(struct cifs_open_info_data *data,
+                                       struct super_block *sb,
+                                       const unsigned int xid,
+                                       struct cifs_tcon *tcon,
+                                       const char *full_path,
+                                       bool directory,
+                                       struct kvec *reparse_iov,
+                                       struct kvec *xattr_iov)
+{
+       struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
+       struct cifs_open_parms oparms;
+       TRANSACT_IOCTL_REQ *io_req;
+       struct inode *new = NULL;
+       struct kvec in_iov[2];
+       struct kvec out_iov;
+       struct cifs_fid fid;
+       int io_req_len;
+       int oplock = 0;
+       int buf_type = 0;
+       int rc;
+
+       cifs_tcon_dbg(FYI, "%s: path=%s\n", __func__, full_path);
+
+       /*
+        * If server filesystem does not support reparse points then do not
+        * attempt to create reparse point. This will prevent creating unusable
+        * empty object on the server.
+        */
+       if (!(le32_to_cpu(tcon->fsAttrInfo.Attributes) & FILE_SUPPORTS_REPARSE_POINTS))
+               return ERR_PTR(-EOPNOTSUPP);
+
+#ifndef CONFIG_CIFS_XATTR
+       if (xattr_iov)
+               return ERR_PTR(-EOPNOTSUPP);
+#endif
+
+       oparms = CIFS_OPARMS(cifs_sb, tcon, full_path,
+                            FILE_READ_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA,
+                            FILE_CREATE,
+                            (directory ? CREATE_NOT_FILE : CREATE_NOT_DIR) | OPEN_REPARSE_POINT,
+                            ACL_NO_MODE);
+       oparms.fid = &fid;
+
+       rc = CIFS_open(xid, &oparms, &oplock, NULL);
+       if (rc)
+               return ERR_PTR(rc);
+
+#ifdef CONFIG_CIFS_XATTR
+       if (xattr_iov) {
+               struct smb2_file_full_ea_info *ea;
+
+               ea = &((struct smb2_create_ea_ctx *)xattr_iov->iov_base)->ea;
+               while (1) {
+                       rc = CIFSSMBSetEA(xid,
+                                         tcon,
+                                         full_path,
+                                         &ea->ea_data[0],
+                                         &ea->ea_data[ea->ea_name_length+1],
+                                         le16_to_cpu(ea->ea_value_length),
+                                         cifs_sb->local_nls,
+                                         cifs_sb);
+                       if (rc)
+                               goto out_close;
+                       if (le32_to_cpu(ea->next_entry_offset) == 0)
+                               break;
+                       ea = (struct smb2_file_full_ea_info *)((u8 *)ea +
+                               le32_to_cpu(ea->next_entry_offset));
+               }
+       }
+#endif
+
+       rc = smb_init(SMB_COM_NT_TRANSACT, 23, tcon, (void **)&io_req, NULL);
+       if (rc)
+               goto out_close;
+
+       inc_rfc1001_len(io_req, sizeof(io_req->Pad));
+
+       io_req_len = be32_to_cpu(io_req->hdr.smb_buf_length) + sizeof(io_req->hdr.smb_buf_length);
+
+       /* NT IOCTL response contains one-word long output setup buffer with size of output data. */
+       io_req->MaxSetupCount = 1;
+       /* NT IOCTL response does not contain output parameters. */
+       io_req->MaxParameterCount = cpu_to_le32(0);
+       /* FSCTL_SET_REPARSE_POINT response contains empty output data. */
+       io_req->MaxDataCount = cpu_to_le32(0);
+
+       io_req->TotalParameterCount = cpu_to_le32(0);
+       io_req->TotalDataCount = cpu_to_le32(reparse_iov->iov_len);
+       io_req->ParameterCount = io_req->TotalParameterCount;
+       io_req->ParameterOffset = cpu_to_le32(0);
+       io_req->DataCount = io_req->TotalDataCount;
+       io_req->DataOffset = cpu_to_le32(offsetof(typeof(*io_req), Data) -
+                                        sizeof(io_req->hdr.smb_buf_length));
+       io_req->SetupCount = 4;
+       io_req->SubCommand = cpu_to_le16(NT_TRANSACT_IOCTL);
+       io_req->FunctionCode = cpu_to_le32(FSCTL_SET_REPARSE_POINT);
+       io_req->Fid = fid.netfid;
+       io_req->IsFsctl = 1;
+       io_req->IsRootFlag = 0;
+       io_req->ByteCount = cpu_to_le16(le32_to_cpu(io_req->DataCount) + sizeof(io_req->Pad));
+
+       inc_rfc1001_len(io_req, reparse_iov->iov_len);
+
+       in_iov[0].iov_base = (char *)io_req;
+       in_iov[0].iov_len = io_req_len;
+       in_iov[1] = *reparse_iov;
+       rc = SendReceive2(xid, tcon->ses, in_iov, ARRAY_SIZE(in_iov), &buf_type,
+                         CIFS_NO_RSP_BUF, &out_iov);
+
+       cifs_buf_release(io_req);
+
+       if (!rc)
+               rc = cifs_get_inode_info(&new, full_path, data, sb, xid, NULL);
+
+out_close:
+       CIFSSMBClose(xid, tcon, fid.netfid);
+
+       /*
+        * If CREATE was successful but FSCTL_SET_REPARSE_POINT failed then
+        * remove the intermediate object created by CREATE. Otherwise
+        * empty object stay on the server when reparse call failed.
+        */
+       if (rc)
+               CIFSSMBDelFile(xid, tcon, full_path, cifs_sb, NULL);
+
+       return rc ? ERR_PTR(rc) : new;
+}
+
 int
 CIFSSMB_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
                    __u16 fid)
index 769752ad2c5ce8d38f8cc735a88719c34881379d..2ecd705e9e8c1bae64bb1b648c9f5c226f5021a3 100644 (file)
@@ -19,6 +19,7 @@
 #include "smb2proto.h"
 #include "cifs_ioctl.h"
 #include "fs_context.h"
+#include "reparse.h"
 
 /*
  * M-F Symlink Functions - Begin
@@ -570,7 +571,6 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
        int rc = -EOPNOTSUPP;
        unsigned int xid;
        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
-       struct TCP_Server_Info *server;
        struct tcon_link *tlink;
        struct cifs_tcon *pTcon;
        const char *full_path;
@@ -593,7 +593,6 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
                goto symlink_exit;
        }
        pTcon = tlink_tcon(tlink);
-       server = cifs_pick_channel(pTcon->ses);
 
        full_path = build_path_from_dentry(direntry, page);
        if (IS_ERR(full_path)) {
@@ -643,13 +642,9 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
        case CIFS_SYMLINK_TYPE_NATIVE:
        case CIFS_SYMLINK_TYPE_NFS:
        case CIFS_SYMLINK_TYPE_WSL:
-               if (server->ops->create_reparse_symlink &&
-                   (le32_to_cpu(pTcon->fsAttrInfo.Attributes) & FILE_SUPPORTS_REPARSE_POINTS)) {
-                       rc = server->ops->create_reparse_symlink(xid, inode,
-                                                                direntry,
-                                                                pTcon,
-                                                                full_path,
-                                                                symname);
+               if (le32_to_cpu(pTcon->fsAttrInfo.Attributes) & FILE_SUPPORTS_REPARSE_POINTS) {
+                       rc = create_reparse_symlink(xid, inode, direntry, pTcon,
+                                                   full_path, symname);
                        goto symlink_exit;
                }
                break;
index 5fa29a97ac154b6b4a0c4709f1ad96c565c089f9..33c1d970747cd609b5437cc3f9b170b52fe0ad4a 100644 (file)
@@ -34,7 +34,7 @@ static int detect_directory_symlink_target(struct cifs_sb_info *cifs_sb,
                                           const char *symname,
                                           bool *directory);
 
-int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
+int create_reparse_symlink(const unsigned int xid, struct inode *inode,
                                struct dentry *dentry, struct cifs_tcon *tcon,
                                const char *full_path, const char *symname)
 {
@@ -227,7 +227,8 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode,
 
        iov.iov_base = buf;
        iov.iov_len = len;
-       new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
+       new = tcon->ses->server->ops->create_reparse_inode(
+                                    &data, inode->i_sb, xid,
                                     tcon, full_path, directory,
                                     &iov, NULL);
        if (!IS_ERR(new))
@@ -399,7 +400,8 @@ static int create_native_socket(const unsigned int xid, struct inode *inode,
        struct inode *new;
        int rc = 0;
 
-       new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
+       new = tcon->ses->server->ops->create_reparse_inode(
+                                    &data, inode->i_sb, xid,
                                     tcon, full_path, false, &iov, NULL);
        if (!IS_ERR(new))
                d_instantiate(dentry, new);
@@ -492,7 +494,8 @@ static int mknod_nfs(unsigned int xid, struct inode *inode,
                .symlink_target = kstrdup(symname, GFP_KERNEL),
        };
 
-       new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
+       new = tcon->ses->server->ops->create_reparse_inode(
+                                    &data, inode->i_sb, xid,
                                     tcon, full_path, false, &iov, NULL);
        if (!IS_ERR(new))
                d_instantiate(dentry, new);
@@ -685,7 +688,8 @@ static int mknod_wsl(unsigned int xid, struct inode *inode,
        memcpy(data.wsl.eas, &cc->ea, len);
        data.wsl.eas_len = len;
 
-       new = smb2_get_reparse_inode(&data, inode->i_sb,
+       new = tcon->ses->server->ops->create_reparse_inode(
+                                    &data, inode->i_sb,
                                     xid, tcon, full_path, false,
                                     &reparse_iov, &xattr_iov);
        if (!IS_ERR(new))
@@ -698,7 +702,7 @@ static int mknod_wsl(unsigned int xid, struct inode *inode,
        return rc;
 }
 
-int smb2_mknod_reparse(unsigned int xid, struct inode *inode,
+int mknod_reparse(unsigned int xid, struct inode *inode,
                       struct dentry *dentry, struct cifs_tcon *tcon,
                       const char *full_path, umode_t mode, dev_t dev)
 {
index 08de853b36a8a9c6b7d3fae184bffbf6acb89286..66269c10beba9cbb9e70b54197dc0daecee0e455 100644 (file)
@@ -129,10 +129,10 @@ static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data)
 bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
                                 struct cifs_fattr *fattr,
                                 struct cifs_open_info_data *data);
-int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
+int create_reparse_symlink(const unsigned int xid, struct inode *inode,
                                struct dentry *dentry, struct cifs_tcon *tcon,
                                const char *full_path, const char *symname);
-int smb2_mknod_reparse(unsigned int xid, struct inode *inode,
+int mknod_reparse(unsigned int xid, struct inode *inode,
                       struct dentry *dentry, struct cifs_tcon *tcon,
                       const char *full_path, umode_t mode, dev_t dev);
 struct reparse_data_buffer *smb2_get_reparse_point_buffer(const struct kvec *rsp_iov, u32 *len);
index b27a182629ece9eeb812376d0a1690c9d60803f0..e364b6515af3f67fcca7036ef4954fbcfa1591e5 100644 (file)
@@ -16,6 +16,7 @@
 #include "fs_context.h"
 #include "nterr.h"
 #include "smberr.h"
+#include "reparse.h"
 
 /*
  * An NT cancel request header looks just like the original request except:
@@ -1263,17 +1264,26 @@ cifs_make_node(unsigned int xid, struct inode *inode,
                if (rc == 0)
                        d_instantiate(dentry, newinode);
                return rc;
+       } else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL) {
+               /*
+                * Check if mounted with mount parm 'sfu' mount parm.
+                * SFU emulation should work with all servers
+                * and was used by default in earlier versions of Windows.
+                */
+               return cifs_sfu_make_node(xid, inode, dentry, tcon,
+                                         full_path, mode, dev);
+       } else if (le32_to_cpu(tcon->fsAttrInfo.Attributes) & FILE_SUPPORTS_REPARSE_POINTS) {
+               /*
+                * mknod via reparse points requires server support for
+                * storing reparse points, which is available since
+                * Windows 2000, but was not widely used until release
+                * of Windows Server 2012 by the Windows NFS server.
+                */
+               return mknod_reparse(xid, inode, dentry, tcon,
+                                    full_path, mode, dev);
+       } else {
+               return -EOPNOTSUPP;
        }
-       /*
-        * Check if mounted with mount parm 'sfu' mount parm.
-        * SFU emulation should work with all servers, but only
-        * supports block and char device, socket & fifo,
-        * and was used by default in earlier versions of Windows
-        */
-       if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL))
-               return -EPERM;
-       return cifs_sfu_make_node(xid, inode, dentry, tcon,
-                                 full_path, mode, dev);
 }
 
 static bool
@@ -1370,6 +1380,7 @@ struct smb_version_operations smb1_operations = {
        .create_hardlink = CIFSCreateHardLink,
        .query_symlink = cifs_query_symlink,
        .get_reparse_point_buffer = cifs_get_reparse_point_buffer,
+       .create_reparse_inode = cifs_create_reparse_inode,
        .open = cifs_open_file,
        .set_fid = cifs_set_fid,
        .close = cifs_close_file,
index 35d00f5aefaec20cb94a6bb221b896d2b1234bba..69d251726c02e030bfff2d5d7d2adf31861b5c9c 100644 (file)
@@ -1321,7 +1321,7 @@ smb2_set_file_info(struct inode *inode, const char *full_path,
        return rc;
 }
 
-struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
+struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data,
                                     struct super_block *sb,
                                     const unsigned int xid,
                                     struct cifs_tcon *tcon,
index 938a8a7c5d2187bb5da561b98596923fadce96d0..1b4a31894f431d807236b77d0f87e478c73becf3 100644 (file)
@@ -5262,7 +5262,7 @@ static int smb2_make_node(unsigned int xid, struct inode *inode,
                                        full_path, mode, dev);
        } else if ((le32_to_cpu(tcon->fsAttrInfo.Attributes) & FILE_SUPPORTS_REPARSE_POINTS)
                || (tcon->posix_extensions)) {
-               rc = smb2_mknod_reparse(xid, inode, dentry, tcon,
+               rc = mknod_reparse(xid, inode, dentry, tcon,
                                        full_path, mode, dev);
        }
        return rc;
@@ -5321,7 +5321,7 @@ struct smb_version_operations smb20_operations = {
        .get_reparse_point_buffer = smb2_get_reparse_point_buffer,
        .query_mf_symlink = smb3_query_mf_symlink,
        .create_mf_symlink = smb3_create_mf_symlink,
-       .create_reparse_symlink = smb2_create_reparse_symlink,
+       .create_reparse_inode = smb2_create_reparse_inode,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,
@@ -5424,7 +5424,7 @@ struct smb_version_operations smb21_operations = {
        .get_reparse_point_buffer = smb2_get_reparse_point_buffer,
        .query_mf_symlink = smb3_query_mf_symlink,
        .create_mf_symlink = smb3_create_mf_symlink,
-       .create_reparse_symlink = smb2_create_reparse_symlink,
+       .create_reparse_inode = smb2_create_reparse_inode,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,
@@ -5531,7 +5531,7 @@ struct smb_version_operations smb30_operations = {
        .get_reparse_point_buffer = smb2_get_reparse_point_buffer,
        .query_mf_symlink = smb3_query_mf_symlink,
        .create_mf_symlink = smb3_create_mf_symlink,
-       .create_reparse_symlink = smb2_create_reparse_symlink,
+       .create_reparse_inode = smb2_create_reparse_inode,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,
@@ -5647,7 +5647,7 @@ struct smb_version_operations smb311_operations = {
        .get_reparse_point_buffer = smb2_get_reparse_point_buffer,
        .query_mf_symlink = smb3_query_mf_symlink,
        .create_mf_symlink = smb3_create_mf_symlink,
-       .create_reparse_symlink = smb2_create_reparse_symlink,
+       .create_reparse_inode = smb2_create_reparse_inode,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,
index 035aa16240535c7f75efa572d29bb65b743f2efb..6e805ece6a7b19ee5897404b90a9221b79d93148 100644 (file)
@@ -54,7 +54,7 @@ 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 inode *smb2_create_reparse_inode(struct cifs_open_info_data *data,
                                     struct super_block *sb,
                                     const unsigned int xid,
                                     struct cifs_tcon *tcon,
@@ -314,9 +314,6 @@ 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_symlink(const unsigned int xid, struct inode *inode,
-                               struct dentry *dentry, struct cifs_tcon *tcon,
-                               const char *full_path, const char *symname);
 int smb2_make_nfs_node(unsigned int xid, struct inode *inode,
                       struct dentry *dentry, struct cifs_tcon *tcon,
                       const char *full_path, umode_t mode, dev_t dev);