CIFS: Implement follow_link for SMB2
authorPavel Shilovsky <pshilovsky@samba.org>
Wed, 14 Aug 2013 15:25:21 +0000 (19:25 +0400)
committerSteve French <smfrench@gmail.com>
Sun, 8 Sep 2013 19:27:34 +0000 (14:27 -0500)
that allows to access files through symlink created on a server.

Acked-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Pavel Shilovsky <pshilovsky@samba.org>
Signed-off-by: Steve French <smfrench@gmail.com>
fs/cifs/cifsglob.h
fs/cifs/inode.c
fs/cifs/link.c
fs/cifs/readdir.c
fs/cifs/smb2file.c
fs/cifs/smb2inode.c
fs/cifs/smb2misc.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c
fs/cifs/smb2pdu.h
fs/cifs/smb2proto.h

index 52ca861ed35e4fe3fbf78ec387ff401fb0e8fb94..ec6c3fbb29ebbccbcb32d718a5f67c1fc313b961 100644 (file)
@@ -308,6 +308,9 @@ struct smb_version_operations {
        int (*create_hardlink)(const unsigned int, struct cifs_tcon *,
                               const char *, const char *,
                               struct cifs_sb_info *);
+       /* query symlink target */
+       int (*query_symlink)(const unsigned int, struct cifs_tcon *,
+                            const char *, char **, struct cifs_sb_info *);
        /* open a file for non-posix mounts */
        int (*open)(const unsigned int, struct cifs_open_parms *,
                    __u32 *, FILE_ALL_INFO *);
index 449b6cf09b09dbc15e90f311e09bc011a0157b44..ec0f3423cdac9ff7091296084388bcfc1a9c4076 100644 (file)
@@ -549,6 +549,10 @@ cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
                 * when Unix extensions are disabled - fake it.
                 */
                fattr->cf_nlink = 2;
+       } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
+               fattr->cf_mode = S_IFLNK;
+               fattr->cf_dtype = DT_LNK;
+               fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks);
        } else {
                fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
                fattr->cf_dtype = DT_REG;
index 562044f700e56bf27997bf7ef7490bddf6bc1c91..7e36ceba0c7a72d797a798de500847d4fa6ac66d 100644 (file)
@@ -509,6 +509,7 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
        struct tcon_link *tlink = NULL;
        struct cifs_tcon *tcon;
+       struct TCP_Server_Info *server;
 
        xid = get_xid();
 
@@ -519,25 +520,7 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
                goto out;
        }
        tcon = tlink_tcon(tlink);
-
-       /*
-        * For now, we just handle symlinks with unix extensions enabled.
-        * Eventually we should handle NTFS reparse points, and MacOS
-        * symlink support. For instance...
-        *
-        * rc = CIFSSMBQueryReparseLinkInfo(...)
-        *
-        * For now, just return -EACCES when the server doesn't support posix
-        * extensions. Note that we still allow querying symlinks when posix
-        * extensions are manually disabled. We could disable these as well
-        * but there doesn't seem to be any harm in allowing the client to
-        * read them.
-        */
-       if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) &&
-           !cap_unix(tcon->ses)) {
-               rc = -EACCES;
-               goto out;
-       }
+       server = tcon->ses->server;
 
        full_path = build_path_from_dentry(direntry);
        if (!full_path)
@@ -559,6 +542,9 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
        if ((rc != 0) && cap_unix(tcon->ses))
                rc = CIFSSMBUnixQuerySymLink(xid, tcon, full_path, &target_path,
                                             cifs_sb->local_nls);
+       else if (rc != 0 && server->ops->query_symlink)
+               rc = server->ops->query_symlink(xid, tcon, full_path,
+                                               &target_path, cifs_sb);
 
        kfree(full_path);
 out:
index 69d2c826a23badc552bb686b518beaea297b473c..42ef03be089f2bc9aaabd2e3f40d2b6d6ac207a8 100644 (file)
@@ -172,6 +172,9 @@ cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
                if (cifs_dfs_is_possible(cifs_sb) &&
                    (fattr->cf_cifsattrs & ATTR_REPARSE))
                        fattr->cf_flags |= CIFS_FATTR_NEED_REVAL;
+       } else if (fattr->cf_cifsattrs & ATTR_REPARSE) {
+               fattr->cf_mode = S_IFLNK;
+               fattr->cf_dtype = DT_LNK;
        } else {
                fattr->cf_mode = S_IFREG | cifs_sb->mnt_file_mode;
                fattr->cf_dtype = DT_REG;
index 04a81a4142c3235f1bb1693b9be4c73580023c2b..020245d5c9a74c3592a8eacee9bf1baf47f9629b 100644 (file)
@@ -86,7 +86,7 @@ smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms,
        if (oparms->tcon->ses->server->capabilities & SMB2_GLOBAL_CAP_LEASING)
                memcpy(smb2_oplock + 1, fid->lease_key, SMB2_LEASE_KEY_SIZE);
 
-       rc = SMB2_open(xid, oparms, smb2_path, smb2_oplock, smb2_data);
+       rc = SMB2_open(xid, oparms, smb2_path, smb2_oplock, smb2_data, NULL);
        if (rc)
                goto out;
 
index c6ec1633309abad6464995e4aad8352129f5fed6..78ff88c467b99b9a17c706032a9ed655f92c58b0 100644 (file)
@@ -60,7 +60,7 @@ smb2_open_op_close(const unsigned int xid, struct cifs_tcon *tcon,
        oparms.fid = &fid;
        oparms.reconnect = false;
 
-       rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL);
+       rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL);
        if (rc) {
                kfree(utf16_path);
                return rc;
@@ -136,7 +136,8 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
                return -ENOMEM;
 
        rc = smb2_open_op_close(xid, tcon, cifs_sb, full_path,
-                               FILE_READ_ATTRIBUTES, FILE_OPEN, 0, smb2_data,
+                               FILE_READ_ATTRIBUTES, FILE_OPEN,
+                               OPEN_REPARSE_POINT, smb2_data,
                                SMB2_OP_QUERY_INFO);
        if (rc)
                goto out;
@@ -191,8 +192,8 @@ smb2_unlink(const unsigned int xid, struct cifs_tcon *tcon, const char *name,
            struct cifs_sb_info *cifs_sb)
 {
        return smb2_open_op_close(xid, tcon, cifs_sb, name, DELETE, FILE_OPEN,
-                                 CREATE_DELETE_ON_CLOSE, NULL,
-                                 SMB2_OP_DELETE);
+                                 CREATE_DELETE_ON_CLOSE | OPEN_REPARSE_POINT,
+                                 NULL, SMB2_OP_DELETE);
 }
 
 static int
index b0c43345cd981c082e3de5965e647a5432197605..6103359fb598b79f79523e4246c7e9874dcefab5 100644 (file)
@@ -171,6 +171,10 @@ smb2_check_message(char *buf, unsigned int length)
        if (4 + len != clc_len) {
                cifs_dbg(FYI, "Calculated size %u length %u mismatch mid %llu\n",
                         clc_len, 4 + len, mid);
+               /* create failed on symlink */
+               if (command == SMB2_CREATE_HE &&
+                   hdr->Status == STATUS_STOPPED_ON_SYMLINK)
+                       return 0;
                /* Windows 7 server returns 24 bytes more */
                if (clc_len + 20 == len && command == SMB2_OPLOCK_BREAK_HE)
                        return 0;
index f259e6cc835791f20e34acce582f83ca1b78102c..91b9e5422e9af9a8c1adcc1c2382060c1d33008d 100644 (file)
@@ -24,6 +24,7 @@
 #include "smb2proto.h"
 #include "cifsproto.h"
 #include "cifs_debug.h"
+#include "cifs_unicode.h"
 #include "smb2status.h"
 #include "smb2glob.h"
 
@@ -229,7 +230,7 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
        oparms.fid = &fid;
        oparms.reconnect = false;
 
-       rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL);
+       rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL);
        if (rc) {
                kfree(utf16_path);
                return rc;
@@ -463,7 +464,7 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon,
        oparms.fid = fid;
        oparms.reconnect = false;
 
-       rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL);
+       rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL);
        kfree(utf16_path);
        if (rc) {
                cifs_dbg(VFS, "open dir failed\n");
@@ -550,7 +551,7 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
        oparms.fid = &fid;
        oparms.reconnect = false;
 
-       rc = SMB2_open(xid, &oparms, &srch_path, &oplock, NULL);
+       rc = SMB2_open(xid, &oparms, &srch_path, &oplock, NULL, NULL);
        if (rc)
                return rc;
        buf->f_type = SMB2_MAGIC_NUMBER;
@@ -596,6 +597,57 @@ smb2_new_lease_key(struct cifs_fid *fid)
        get_random_bytes(fid->lease_key, SMB2_LEASE_KEY_SIZE);
 }
 
+static int
+smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon,
+                  const char *full_path, char **target_path,
+                  struct cifs_sb_info *cifs_sb)
+{
+       int rc;
+       __le16 *utf16_path;
+       __u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
+       struct cifs_open_parms oparms;
+       struct cifs_fid fid;
+       struct smb2_err_rsp *err_buf = NULL;
+       struct smb2_symlink_err_rsp *symlink;
+       unsigned int sub_len, sub_offset;
+
+       cifs_dbg(FYI, "%s: path: %s\n", __func__, full_path);
+
+       utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb);
+       if (!utf16_path)
+               return -ENOMEM;
+
+       oparms.tcon = tcon;
+       oparms.desired_access = FILE_READ_ATTRIBUTES;
+       oparms.disposition = FILE_OPEN;
+       oparms.create_options = 0;
+       oparms.fid = &fid;
+       oparms.reconnect = false;
+
+       rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, &err_buf);
+
+       if (!rc || !err_buf) {
+               kfree(utf16_path);
+               return -ENOENT;
+       }
+       /* open must fail on symlink - reset rc */
+       rc = 0;
+       symlink = (struct smb2_symlink_err_rsp *)err_buf->ErrorData;
+       sub_len = le16_to_cpu(symlink->SubstituteNameLength);
+       sub_offset = le16_to_cpu(symlink->SubstituteNameOffset);
+       *target_path = cifs_strndup_from_utf16(
+                               (char *)symlink->PathBuffer + sub_offset,
+                               sub_len, true, cifs_sb->local_nls);
+       if (!(*target_path)) {
+               kfree(utf16_path);
+               return -ENOMEM;
+       }
+       convert_delimiter(*target_path, '/');
+       cifs_dbg(FYI, "%s: target path: %s\n", __func__, *target_path);
+       kfree(utf16_path);
+       return rc;
+}
+
 struct smb_version_operations smb21_operations = {
        .compare_fids = smb2_compare_fids,
        .setup_request = smb2_setup_request,
@@ -638,6 +690,7 @@ struct smb_version_operations smb21_operations = {
        .unlink = smb2_unlink,
        .rename = smb2_rename_path,
        .create_hardlink = smb2_create_hardlink,
+       .query_symlink = smb2_query_symlink,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,
@@ -706,6 +759,7 @@ struct smb_version_operations smb30_operations = {
        .unlink = smb2_unlink,
        .rename = smb2_rename_path,
        .create_hardlink = smb2_create_hardlink,
+       .query_symlink = smb2_query_symlink,
        .open = smb2_open_file,
        .set_fid = smb2_set_fid,
        .close = smb2_close_file,
index abc9c2809b519c50623209d341733c31a752b2b3..5a49861633a62652461ada755e16b187bd32ea1b 100644 (file)
@@ -977,7 +977,8 @@ add_durable_context(struct kvec *iov, unsigned int *num_iovec,
 
 int
 SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
-         __u8 *oplock, struct smb2_file_all_info *buf)
+         __u8 *oplock, struct smb2_file_all_info *buf,
+         struct smb2_err_rsp **err_buf)
 {
        struct smb2_create_req *req;
        struct smb2_create_rsp *rsp;
@@ -1082,6 +1083,9 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
 
        if (rc != 0) {
                cifs_stats_fail_inc(tcon, SMB2_CREATE_HE);
+               if (err_buf)
+                       *err_buf = kmemdup(rsp, get_rfc1002_length(rsp) + 4,
+                                          GFP_KERNEL);
                goto creat_exit;
        }
 
index 36b0d37ea69b8d9f7bab7d1e1e3fcd167afe34e3..40baeae60b081aa8e94757bf992f1f612f26a927 100644 (file)
@@ -150,6 +150,20 @@ struct smb2_err_rsp {
        __u8   ErrorData[1];  /* variable length */
 } __packed;
 
+struct smb2_symlink_err_rsp {
+       __le32 SymLinkLength;
+       __le32 SymLinkErrorTag;
+       __le32 ReparseTag;
+       __le16 ReparseDataLength;
+       __le16 UnparsedPathLength;
+       __le16 SubstituteNameOffset;
+       __le16 SubstituteNameLength;
+       __le16 PrintNameOffset;
+       __le16 PrintNameLength;
+       __le32 Flags;
+       __u8  PathBuffer[0];
+} __packed;
+
 #define SMB2_CLIENT_GUID_SIZE 16
 
 extern __u8 cifs_client_guid[SMB2_CLIENT_GUID_SIZE];
index 1a5ecbed40edac9ae352a1baa2c162697712e9fd..1db89fda1392be62d8a5dd09e4f30bad54346f0d 100644 (file)
@@ -106,7 +106,8 @@ extern int SMB2_tcon(const unsigned int xid, struct cifs_ses *ses,
 extern int SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon);
 extern int SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms,
                     __le16 *path, __u8 *oplock,
-                    struct smb2_file_all_info *buf);
+                    struct smb2_file_all_info *buf,
+                    struct smb2_err_rsp **err_buf);
 extern int SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon,
                     u64 persistent_fid, u64 volatile_fid, u32 opcode,
                     bool is_fsctl, char *in_data, u32 indatalen,