cifs: Add support for reading SFU symlink location
authorPali Rohár <pali@kernel.org>
Thu, 12 Sep 2024 12:05:43 +0000 (14:05 +0200)
committerSteve French <stfrench@microsoft.com>
Sun, 15 Sep 2024 15:42:45 +0000 (10:42 -0500)
Currently when sfu mount option is specified then CIFS can recognize SFU
symlink, but is not able to read symlink target location. readlink()
syscall just returns that operation is not supported.

Implement this missing functionality in cifs_sfu_type() function. Read
target location of SFU-style symlink, parse it and fill into fattr's
cf_symlink_target member.

SFU-style symlink is file which has system attribute set and file content
is buffer "IntxLNK\1" (8th byte is 0x01) followed by the target location
encoded in little endian UCS-2/UTF-16. This format was introduced in
Interix 3.0 subsystem, as part of the Microsoft SFU 3.0 and is used also by
all later versions. Previous versions had no symlink support.

Signed-off-by: Pali Rohár <pali@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/inode.c

index 7d424e769a5670454f84abac599307d19e0fecb6..e8567ed63f223275117c14ee0c3f04ade1b98847 100644 (file)
@@ -529,6 +529,8 @@ cifs_sfu_type(struct cifs_fattr *fattr, const char *path,
        struct cifs_fid fid;
        struct cifs_open_parms oparms;
        struct cifs_io_parms io_parms = {0};
+       char *symlink_buf_utf16;
+       unsigned int symlink_len_utf16;
        char buf[24];
        unsigned int bytes_read;
        char *pbuf;
@@ -616,6 +618,33 @@ cifs_sfu_type(struct cifs_fattr *fattr, const char *path,
                        cifs_dbg(FYI, "Symlink\n");
                        fattr->cf_mode |= S_IFLNK;
                        fattr->cf_dtype = DT_LNK;
+                       if ((fattr->cf_eof > 8) && (fattr->cf_eof % 2 == 0)) {
+                               symlink_buf_utf16 = kmalloc(fattr->cf_eof-8 + 1, GFP_KERNEL);
+                               if (symlink_buf_utf16) {
+                                       io_parms.offset = 8;
+                                       io_parms.length = fattr->cf_eof-8 + 1;
+                                       buf_type = CIFS_NO_BUFFER;
+                                       rc = tcon->ses->server->ops->sync_read(xid, &fid, &io_parms,
+                                                                              &symlink_len_utf16,
+                                                                              &symlink_buf_utf16,
+                                                                              &buf_type);
+                                       if ((rc == 0) &&
+                                           (symlink_len_utf16 > 0) &&
+                                           (symlink_len_utf16 < fattr->cf_eof-8 + 1) &&
+                                           (symlink_len_utf16 % 2 == 0)) {
+                                               fattr->cf_symlink_target =
+                                                       cifs_strndup_from_utf16(symlink_buf_utf16,
+                                                                               symlink_len_utf16,
+                                                                               true,
+                                                                               cifs_sb->local_nls);
+                                               if (!fattr->cf_symlink_target)
+                                                       rc = -ENOMEM;
+                                       }
+                                       kfree(symlink_buf_utf16);
+                               } else {
+                                       rc = -ENOMEM;
+                               }
+                       }
                } else if (memcmp("LnxFIFO", pbuf, 8) == 0) {
                        cifs_dbg(FYI, "FIFO\n");
                        fattr->cf_mode |= S_IFIFO;