]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
smb: client: allow creating symlinks via reparse points
authorPaulo Alcantara <pc@manguebit.com>
Sun, 26 Nov 2023 02:55:04 +0000 (23:55 -0300)
committerSteve French <stfrench@microsoft.com>
Sun, 7 Jan 2024 21:46:06 +0000 (15:46 -0600)
Add support for creating symlinks via IO_REPARSE_TAG_SYMLINK reparse
points in SMB2+.

These are fully supported by most SMB servers and documented in
MS-FSCC.  Also have the advantage of requiring fewer roundtrips as
their symlink targets can be parsed directly from CREATE responses on
STATUS_STOPPED_ON_SYMLINK errors.

Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202311260838.nx5mkj1j-lkp@intel.com/
Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsglob.h
fs/smb/client/link.c
fs/smb/client/smb2ops.c

index d5d6aedef55812e53df5e8896321a37743bca58c..f840756e0169d64fc69ce63b79c2a59013bd71ca 100644 (file)
@@ -574,6 +574,12 @@ struct smb_version_operations {
        int (*parse_reparse_point)(struct cifs_sb_info *cifs_sb,
                                   struct kvec *rsp_iov,
                                   struct cifs_open_info_data *data);
+       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 smb_version_values {
index 691f43a1ec2bcebd326e5a9f9542f80087b9ae9e..d86da949a919056b23b68f400b36933a94e9635f 100644 (file)
@@ -569,6 +569,7 @@ 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;
@@ -590,6 +591,7 @@ 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)) {
@@ -601,17 +603,20 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
        cifs_dbg(FYI, "symname is %s\n", symname);
 
        /* BB what if DFS and this volume is on different share? BB */
-       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS)
+       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) {
                rc = create_mf_symlink(xid, pTcon, cifs_sb, full_path, symname);
 #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
-       else if (pTcon->unix_ext)
+       } else if (pTcon->unix_ext) {
                rc = CIFSUnixCreateSymLink(xid, pTcon, full_path, symname,
                                           cifs_sb->local_nls,
                                           cifs_remap(cifs_sb));
 #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
-       /* else
-          rc = CIFSCreateReparseSymLink(xid, pTcon, fromName, toName,
-                                       cifs_sb_target->local_nls); */
+       } else if (server->ops->create_reparse_symlink) {
+               rc =  server->ops->create_reparse_symlink(xid, inode, direntry,
+                                                         pTcon, full_path,
+                                                         symname);
+               goto symlink_exit;
+       }
 
        if (rc == 0) {
                if (pTcon->posix_extensions) {
index 201e951d50e5e4720d23c9cc992f29edd0d9f70b..8a9029bd9f0fc9f4832aec061d7db9e9fd3955dc 100644 (file)
@@ -5247,6 +5247,72 @@ static int nfs_make_node(unsigned int xid, struct inode *inode,
        return rc;
 }
 
+static 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)
+{
+       struct reparse_symlink_data_buffer *buf = NULL;
+       struct cifs_open_info_data data;
+       struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       struct inode *new;
+       struct kvec iov;
+       __le16 *path;
+       char *sym;
+       u16 len, plen;
+       int rc = 0;
+
+       sym = kstrdup(symname, GFP_KERNEL);
+       if (!sym)
+               return -ENOMEM;
+
+       data = (struct cifs_open_info_data) {
+               .reparse_point = true,
+               .reparse = { .tag = IO_REPARSE_TAG_SYMLINK, },
+               .symlink_target = sym,
+       };
+
+       path = cifs_convert_path_to_utf16(symname, cifs_sb);
+       if (!path) {
+               rc = -ENOMEM;
+               goto out;
+       }
+
+       plen = 2 * UniStrnlen((wchar_t *)path, PATH_MAX);
+       len = sizeof(*buf) + plen * 2;
+       buf = kzalloc(len, GFP_KERNEL);
+       if (!buf) {
+               rc = -ENOMEM;
+               goto out;
+       }
+
+       buf->ReparseTag = cpu_to_le32(IO_REPARSE_TAG_SYMLINK);
+       buf->ReparseDataLength = cpu_to_le16(len - sizeof(struct reparse_data_buffer));
+       buf->SubstituteNameOffset = cpu_to_le16(plen);
+       buf->SubstituteNameLength = cpu_to_le16(plen);
+       memcpy(&buf->PathBuffer[plen], path, plen);
+       buf->PrintNameOffset = 0;
+       buf->PrintNameLength = cpu_to_le16(plen);
+       memcpy(buf->PathBuffer, path, plen);
+       buf->Flags = cpu_to_le32(*symname != '/' ? SYMLINK_FLAG_RELATIVE : 0);
+
+       iov.iov_base = buf;
+       iov.iov_len = len;
+       new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
+                                    tcon, full_path, &iov);
+       if (!IS_ERR(new))
+               d_instantiate(dentry, new);
+       else
+               rc = PTR_ERR(new);
+out:
+       kfree(path);
+       cifs_free_open_info(&data);
+       kfree(buf);
+       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)
@@ -5323,6 +5389,7 @@ struct smb_version_operations smb20_operations = {
        .parse_reparse_point = smb2_parse_reparse_point,
        .query_mf_symlink = smb3_query_mf_symlink,
        .create_mf_symlink = smb3_create_mf_symlink,
+       .create_reparse_symlink = smb2_create_reparse_symlink,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,
@@ -5425,6 +5492,7 @@ struct smb_version_operations smb21_operations = {
        .parse_reparse_point = smb2_parse_reparse_point,
        .query_mf_symlink = smb3_query_mf_symlink,
        .create_mf_symlink = smb3_create_mf_symlink,
+       .create_reparse_symlink = smb2_create_reparse_symlink,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,
@@ -5530,6 +5598,7 @@ struct smb_version_operations smb30_operations = {
        .parse_reparse_point = smb2_parse_reparse_point,
        .query_mf_symlink = smb3_query_mf_symlink,
        .create_mf_symlink = smb3_create_mf_symlink,
+       .create_reparse_symlink = smb2_create_reparse_symlink,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,
@@ -5644,6 +5713,7 @@ struct smb_version_operations smb311_operations = {
        .parse_reparse_point = smb2_parse_reparse_point,
        .query_mf_symlink = smb3_query_mf_symlink,
        .create_mf_symlink = smb3_create_mf_symlink,
+       .create_reparse_symlink = smb2_create_reparse_symlink,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,