smb: client: allow creating symlinks via reparse points
authorPaulo Alcantara <pc@manguebit.com>
Sun, 26 Nov 2023 02:55:04 +0000 (23:55 -0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 25 May 2024 14:22:43 +0000 (16:22 +0200)
[ Upstream commit 514d793e27a310eb26b112c1f8f1a160472907e5 ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/smb/client/cifsglob.h
fs/smb/client/link.c
fs/smb/client/smb2ops.c

index 3e7c3c3d73a73feb23b90e054e8f6193a8c1504f..414648bf816b2d5f63a7aab3f6ee6097c58a7c71 100644 (file)
@@ -577,6 +577,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 4fed19d5a81d451b8da1319998fe4211d580aad6..c5957deb1a859e3d7a41956933e46cd21af471fa 100644 (file)
@@ -5246,6 +5246,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)
@@ -5322,6 +5388,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,
@@ -5424,6 +5491,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,
@@ -5645,6 +5714,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,