SMB311: Add support for query info using posix extensions (level 100)
authorSteve French <stfrench@microsoft.com>
Fri, 12 Jun 2020 00:25:47 +0000 (19:25 -0500)
committerSteve French <stfrench@microsoft.com>
Fri, 12 Jun 2020 11:20:38 +0000 (06:20 -0500)
Adds support for better query info on dentry revalidation (using
the SMB3.1.1 POSIX extensions level 100).  Followon patch will
add support for translating the UID/GID from the SID and also
will add support for using the posix query info on lookup.

Signed-off-by: Steve French <stfrench@microsoft.com>
Reviewed-by: Ronnie Sahlberg <lsahlber@redhat.com>
Reviewed-by: Aurelien Aptel <aaptel@suse.com>
fs/cifs/cifsproto.h
fs/cifs/inode.c
fs/cifs/smb2glob.h
fs/cifs/smb2inode.c
fs/cifs/smb2pdu.h
fs/cifs/smb2proto.h

index bd92070ca30c8e8e387f9c1425447c807f88b8a2..b603da73f4f50d01ab6769b715e4ce59eba8a35c 100644 (file)
@@ -198,6 +198,8 @@ extern struct inode *cifs_iget(struct super_block *sb,
 extern int cifs_get_inode_info(struct inode **inode, const char *full_path,
                               FILE_ALL_INFO *data, struct super_block *sb,
                               int xid, const struct cifs_fid *fid);
 extern int cifs_get_inode_info(struct inode **inode, const char *full_path,
                               FILE_ALL_INFO *data, struct super_block *sb,
                               int xid, const struct cifs_fid *fid);
+extern int smb311_posix_get_inode_info(struct inode **pinode, const char *search_path,
+                       struct super_block *sb, unsigned int xid);
 extern int cifs_get_inode_info_unix(struct inode **pinode,
                        const unsigned char *search_path,
                        struct super_block *sb, unsigned int xid);
 extern int cifs_get_inode_info_unix(struct inode **pinode,
                        const unsigned char *search_path,
                        struct super_block *sb, unsigned int xid);
index 5072bcaf4be11421dd277a68a72847f4c92ab4bc..c367195bdb0837768ed5007d86825b1d30f8ca09 100644 (file)
@@ -32,6 +32,7 @@
 #include "cifspdu.h"
 #include "cifsglob.h"
 #include "cifsproto.h"
 #include "cifspdu.h"
 #include "cifsglob.h"
 #include "cifsproto.h"
+#include "smb2proto.h"
 #include "cifs_debug.h"
 #include "cifs_fs_sb.h"
 #include "cifs_unicode.h"
 #include "cifs_debug.h"
 #include "cifs_fs_sb.h"
 #include "cifs_unicode.h"
@@ -595,6 +596,62 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
 #endif
 }
 
 #endif
 }
 
+/* Fill a cifs_fattr struct with info from POSIX info struct */
+static void
+smb311_posix_info_to_fattr(struct cifs_fattr *fattr, struct smb311_posix_qinfo *info,
+                          struct super_block *sb, bool adjust_tz, bool symlink)
+{
+       struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
+       struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+
+       memset(fattr, 0, sizeof(*fattr));
+
+       /* no fattr->flags to set */
+       fattr->cf_cifsattrs = le32_to_cpu(info->DosAttributes);
+       fattr->cf_uniqueid = le64_to_cpu(info->Inode);
+
+       if (info->LastAccessTime)
+               fattr->cf_atime = cifs_NTtimeToUnix(info->LastAccessTime);
+       else
+               ktime_get_coarse_real_ts64(&fattr->cf_atime);
+
+       fattr->cf_ctime = cifs_NTtimeToUnix(info->ChangeTime);
+       fattr->cf_mtime = cifs_NTtimeToUnix(info->LastWriteTime);
+
+       if (adjust_tz) {
+               fattr->cf_ctime.tv_sec += tcon->ses->server->timeAdj;
+               fattr->cf_mtime.tv_sec += tcon->ses->server->timeAdj;
+       }
+
+       fattr->cf_eof = le64_to_cpu(info->EndOfFile);
+       fattr->cf_bytes = le64_to_cpu(info->AllocationSize);
+       fattr->cf_createtime = le64_to_cpu(info->CreationTime);
+
+       fattr->cf_nlink = le32_to_cpu(info->HardLinks);
+       fattr->cf_mode = (umode_t) le32_to_cpu(info->Mode);
+       /* The srv fs device id is overridden on network mount so setting rdev isn't needed here */
+       /* fattr->cf_rdev = le32_to_cpu(info->DeviceId); */
+
+       if (symlink) {
+               fattr->cf_mode |= S_IFLNK;
+               fattr->cf_dtype = DT_LNK;
+       } else if (fattr->cf_cifsattrs & ATTR_DIRECTORY) {
+               fattr->cf_mode |= S_IFDIR;
+               fattr->cf_dtype = DT_DIR;
+       } else { /* file */
+               fattr->cf_mode |= S_IFREG;
+               fattr->cf_dtype = DT_REG;
+       }
+       /* else if reparse point ... TODO: add support for FIFO and blk dev; special file types */
+
+       fattr->cf_uid = cifs_sb->mnt_uid; /* TODO: map uid and gid from SID */
+       fattr->cf_gid = cifs_sb->mnt_gid;
+
+       cifs_dbg(FYI, "POSIX query info: mode 0x%x uniqueid 0x%llx nlink %d\n",
+               fattr->cf_mode, fattr->cf_uniqueid, fattr->cf_nlink);
+}
+
+
 /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
 static void
 cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
 /* Fill a cifs_fattr struct with info from FILE_ALL_INFO */
 static void
 cifs_all_info_to_fattr(struct cifs_fattr *fattr, FILE_ALL_INFO *info,
@@ -1023,6 +1080,121 @@ out:
        return rc;
 }
 
        return rc;
 }
 
+int
+smb311_posix_get_inode_info(struct inode **inode,
+                   const char *full_path,
+                   struct super_block *sb, unsigned int xid)
+{
+       struct cifs_tcon *tcon;
+       struct TCP_Server_Info *server;
+       struct tcon_link *tlink;
+       struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
+       bool adjust_tz = false;
+       struct cifs_fattr fattr = {0};
+       bool symlink = false;
+       struct smb311_posix_qinfo *data = NULL;
+       int rc = 0;
+       int tmprc = 0;
+
+       tlink = cifs_sb_tlink(cifs_sb);
+       if (IS_ERR(tlink))
+               return PTR_ERR(tlink);
+       tcon = tlink_tcon(tlink);
+       server = tcon->ses->server;
+
+       /*
+        * 1. Fetch file metadata
+        */
+
+       if (is_inode_cache_good(*inode)) {
+               cifs_dbg(FYI, "No need to revalidate cached inode sizes\n");
+               goto out;
+       }
+       data = kmalloc(sizeof(struct smb311_posix_qinfo), GFP_KERNEL);
+       if (!data) {
+               rc = -ENOMEM;
+               goto out;
+       }
+
+       rc = smb311_posix_query_path_info(xid, tcon, cifs_sb,
+                                                 full_path, data,
+                                                 &adjust_tz, &symlink);
+
+       /*
+        * 2. Convert it to internal cifs metadata (fattr)
+        */
+
+       switch (rc) {
+       case 0:
+               smb311_posix_info_to_fattr(&fattr, data, sb, adjust_tz, symlink);
+               break;
+       case -EREMOTE:
+               /* DFS link, no metadata available on this server */
+               cifs_create_dfs_fattr(&fattr, sb);
+               rc = 0;
+               break;
+       case -EACCES:
+               /*
+                * For SMB2 and later the backup intent flag
+                * is already sent if needed on open and there
+                * is no path based FindFirst operation to use
+                * to retry with so nothing we can do, bail out
+                */
+               goto out;
+       default:
+               cifs_dbg(FYI, "%s: unhandled err rc %d\n", __func__, rc);
+               goto out;
+       }
+
+
+       /*
+        * 4. Tweak fattr based on mount options
+        */
+
+       /* check for Minshall+French symlinks */
+       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) {
+               tmprc = check_mf_symlink(xid, tcon, cifs_sb, &fattr,
+                                        full_path);
+               if (tmprc)
+                       cifs_dbg(FYI, "check_mf_symlink: %d\n", tmprc);
+       }
+
+       /*
+        * 5. Update inode with final fattr data
+        */
+
+       if (!*inode) {
+               *inode = cifs_iget(sb, &fattr);
+               if (!*inode)
+                       rc = -ENOMEM;
+       } else {
+               /* we already have inode, update it */
+
+               /* if uniqueid is different, return error */
+               if (unlikely(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM &&
+                   CIFS_I(*inode)->uniqueid != fattr.cf_uniqueid)) {
+                       CIFS_I(*inode)->time = 0; /* force reval */
+                       rc = -ESTALE;
+                       goto out;
+               }
+
+               /* if filetype is different, return error */
+               if (unlikely(((*inode)->i_mode & S_IFMT) !=
+                   (fattr.cf_mode & S_IFMT))) {
+                       CIFS_I(*inode)->time = 0; /* force reval */
+                       rc = -ESTALE;
+                       goto out;
+               }
+
+               cifs_fattr_to_inode(*inode, &fattr);
+       }
+out:
+       cifs_put_tlink(tlink);
+       kfree(data);
+       return rc;
+}
+
+
 static const struct inode_operations cifs_ipc_inode_ops = {
        .lookup = cifs_lookup,
 };
 static const struct inode_operations cifs_ipc_inode_ops = {
        .lookup = cifs_lookup,
 };
@@ -2114,7 +2286,9 @@ int cifs_revalidate_dentry_attr(struct dentry *dentry)
                 dentry, cifs_get_time(dentry), jiffies);
 
 again:
                 dentry, cifs_get_time(dentry), jiffies);
 
 again:
-       if (cifs_sb_master_tcon(CIFS_SB(sb))->unix_ext)
+       if (cifs_sb_master_tcon(CIFS_SB(sb))->posix_extensions)
+               rc = smb311_posix_get_inode_info(&inode, full_path, sb, xid);
+       else if (cifs_sb_master_tcon(CIFS_SB(sb))->unix_ext)
                rc = cifs_get_inode_info_unix(&inode, full_path, sb, xid);
        else
                rc = cifs_get_inode_info(&inode, full_path, NULL, sb,
                rc = cifs_get_inode_info_unix(&inode, full_path, sb, xid);
        else
                rc = cifs_get_inode_info(&inode, full_path, NULL, sb,
index dd10f0ce4cd5798c1d19aa1c5ef81c2ec2239d33..cf20f0b5d8363dd0f2dfe9214db091fe3486268c 100644 (file)
@@ -45,6 +45,7 @@
 #define SMB2_OP_HARDLINK 8
 #define SMB2_OP_SET_EOF 9
 #define SMB2_OP_RMDIR 10
 #define SMB2_OP_HARDLINK 8
 #define SMB2_OP_SET_EOF 9
 #define SMB2_OP_RMDIR 10
+#define SMB2_OP_POSIX_QUERY_INFO 11
 
 /* Used when constructing chained read requests. */
 #define CHAINED_REQUEST 1
 
 /* Used when constructing chained read requests. */
 #define CHAINED_REQUEST 1
index 0a116fc490a9cb3e4e43fd440e975f0cf3427954..5154956311f0c90ae16729813670c78ab78d2ec1 100644 (file)
@@ -160,6 +160,41 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
                        }
                }
 
                        }
                }
 
+               if (rc)
+                       goto finished;
+               num_rqst++;
+               trace_smb3_query_info_compound_enter(xid, ses->Suid, tcon->tid,
+                                                    full_path);
+               break;
+       case SMB2_OP_POSIX_QUERY_INFO:
+               rqst[num_rqst].rq_iov = &vars->qi_iov[0];
+               rqst[num_rqst].rq_nvec = 1;
+
+               if (cfile)
+                       rc = SMB2_query_info_init(tcon, server,
+                               &rqst[num_rqst],
+                               cfile->fid.persistent_fid,
+                               cfile->fid.volatile_fid,
+                               SMB_FIND_FILE_POSIX_INFO,
+                               SMB2_O_INFO_FILE, 0,
+                               /* TBD: fix following to allow for longer SIDs */
+                               sizeof(struct smb311_posix_qinfo *) + (PATH_MAX * 2) +
+                               (sizeof(struct cifs_sid) * 2), 0, NULL);
+               else {
+                       rc = SMB2_query_info_init(tcon, server,
+                               &rqst[num_rqst],
+                               COMPOUND_FID,
+                               COMPOUND_FID,
+                               SMB_FIND_FILE_POSIX_INFO,
+                               SMB2_O_INFO_FILE, 0,
+                               sizeof(struct smb311_posix_qinfo *) + (PATH_MAX * 2) +
+                               (sizeof(struct cifs_sid) * 2), 0, NULL);
+                       if (!rc) {
+                               smb2_set_next_command(tcon, &rqst[num_rqst]);
+                               smb2_set_related(&rqst[num_rqst]);
+                       }
+               }
+
                if (rc)
                        goto finished;
                num_rqst++;
                if (rc)
                        goto finished;
                num_rqst++;
@@ -379,6 +414,26 @@ smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
                        trace_smb3_query_info_compound_done(xid, ses->Suid,
                                                tcon->tid);
                break;
                        trace_smb3_query_info_compound_done(xid, ses->Suid,
                                                tcon->tid);
                break;
+       case SMB2_OP_POSIX_QUERY_INFO:
+               if (rc == 0) {
+                       qi_rsp = (struct smb2_query_info_rsp *)
+                               rsp_iov[1].iov_base;
+                       rc = smb2_validate_and_copy_iov(
+                               le16_to_cpu(qi_rsp->OutputBufferOffset),
+                               le32_to_cpu(qi_rsp->OutputBufferLength),
+                               &rsp_iov[1], sizeof(struct smb311_posix_qinfo) /* add SIDs */, ptr);
+               }
+               if (rqst[1].rq_iov)
+                       SMB2_query_info_free(&rqst[1]);
+               if (rqst[2].rq_iov)
+                       SMB2_close_free(&rqst[2]);
+               if (rc)
+                       trace_smb3_query_info_compound_err(xid,  ses->Suid,
+                                               tcon->tid, rc);
+               else
+                       trace_smb3_query_info_compound_done(xid, ses->Suid,
+                                               tcon->tid);
+               break;
        case SMB2_OP_DELETE:
                if (rc)
                        trace_smb3_delete_err(xid,  ses->Suid, tcon->tid, rc);
        case SMB2_OP_DELETE:
                if (rc)
                        trace_smb3_delete_err(xid,  ses->Suid, tcon->tid, rc);
@@ -512,6 +567,59 @@ out:
        return rc;
 }
 
        return rc;
 }
 
+
+int
+smb311_posix_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
+                    struct cifs_sb_info *cifs_sb, const char *full_path,
+                    struct smb311_posix_qinfo *data, bool *adjust_tz, bool *symlink)
+{
+       int rc;
+       __u32 create_options = 0;
+       struct cifsFileInfo *cfile;
+       struct smb311_posix_qinfo *smb2_data;
+
+       *adjust_tz = false;
+       *symlink = false;
+
+       /* BB TODO: Make struct larger when add support for parsing owner SIDs */
+       smb2_data = kzalloc(sizeof(struct smb311_posix_qinfo),
+                           GFP_KERNEL);
+       if (smb2_data == NULL)
+               return -ENOMEM;
+
+       /*
+        * BB TODO: Add support for using the cached root handle.
+        * Create SMB2_query_posix_info worker function to do non-compounded query
+        * when we already have an open file handle for this. For now this is fast enough
+        * (always using the compounded version).
+        */
+
+       cifs_get_readable_path(tcon, full_path, &cfile);
+       rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
+                             FILE_READ_ATTRIBUTES, FILE_OPEN, create_options,
+                             ACL_NO_MODE, smb2_data, SMB2_OP_POSIX_QUERY_INFO, cfile);
+       if (rc == -EOPNOTSUPP) {
+               /* BB TODO: When support for special files added to Samba re-verify this path */
+               *symlink = true;
+               create_options |= OPEN_REPARSE_POINT;
+
+               /* Failed on a symbolic link - query a reparse point info */
+               rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
+                                     FILE_READ_ATTRIBUTES, FILE_OPEN,
+                                     create_options, ACL_NO_MODE,
+                                     smb2_data, SMB2_OP_POSIX_QUERY_INFO, NULL);
+       }
+       if (rc)
+               goto out;
+
+        /* TODO: will need to allow for the 2 SIDs when add support for getting owner UID/GID */
+       memcpy(data, smb2_data, sizeof(struct smb311_posix_qinfo));
+
+out:
+       kfree(smb2_data);
+       return rc;
+}
+
 int
 smb2_mkdir(const unsigned int xid, struct inode *parent_inode, umode_t mode,
           struct cifs_tcon *tcon, const char *name,
 int
 smb2_mkdir(const unsigned int xid, struct inode *parent_inode, umode_t mode,
           struct cifs_tcon *tcon, const char *name,
index 3b0e6acf9d7d9d4e1995eea37a016aa0d09b3565..236814d612487b59942fa5143231686e75024769 100644 (file)
@@ -1653,7 +1653,7 @@ struct create_posix_rsp {
 } __packed;
 
 /*
 } __packed;
 
 /*
- * SMB2-only POSIX info level
+ * SMB2-only POSIX info level for query dir
  *
  * See posix_info_sid_size(), posix_info_extra_size() and
  * posix_info_parse() to help with the handling of this struct.
  *
  * See posix_info_sid_size(), posix_info_extra_size() and
  * posix_info_parse() to help with the handling of this struct.
@@ -1683,6 +1683,31 @@ struct smb2_posix_info {
         */
 } __packed;
 
         */
 } __packed;
 
+/* Level 100 query info */
+struct smb311_posix_qinfo {
+       __le64 CreationTime;
+       __le64 LastAccessTime;
+       __le64 LastWriteTime;
+       __le64 ChangeTime;
+       __le64 EndOfFile;
+       __le64 AllocationSize;
+       __le32 DosAttributes;
+       __le64 Inode;
+       __le32 DeviceId;
+       __le32 Zero;
+       /* beginning of POSIX Create Context Response */
+       __le32 HardLinks;
+       __le32 ReparseTag;
+       __le32 Mode;
+       u8     Sids[];
+       /*
+        * var sized owner SID
+        * var sized group SID
+        * le32 filenamelength
+        * u8  filename[]
+        */
+} __packed;
+
 /*
  * Parsed version of the above struct. Allows direct access to the
  * variable length fields
 /*
  * Parsed version of the above struct. Allows direct access to the
  * variable length fields
index 71ba74792c9ef713c2911376c4d22adae24da990..a5c6da59847e28465b82c7fc4a48304a79fd2e9b 100644 (file)
@@ -289,6 +289,10 @@ extern int smb2_query_info_compound(const unsigned int xid,
                                    u32 class, u32 type, u32 output_len,
                                    struct kvec *rsp, int *buftype,
                                    struct cifs_sb_info *cifs_sb);
                                    u32 class, u32 type, u32 output_len,
                                    struct kvec *rsp, int *buftype,
                                    struct cifs_sb_info *cifs_sb);
+/* query path info from the server using SMB311 POSIX extensions*/
+extern int smb311_posix_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
+                       struct cifs_sb_info *sb, const char *path, struct smb311_posix_qinfo *qinf,
+                       bool *adjust_tx, bool *symlink);
 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 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);