OSDN Git Service

CIFS: refactor cifs_get_inode_info()
authorAurelien Aptel <aaptel@suse.com>
Mon, 18 Nov 2019 20:04:08 +0000 (21:04 +0100)
committerSteve French <stfrench@microsoft.com>
Mon, 25 Nov 2019 07:16:30 +0000 (01:16 -0600)
Make logic of cifs_get_inode() much clearer by moving code to sub
functions and adding comments.

Document the steps this function does.

cifs_get_inode_info() gets and updates a file inode metadata from its
file path.

* If caller already has raw info data from server they can pass it.
* If inode already exists (just need to update) caller can pass it.

Step 1: get raw data from server if none was passed
Step 2: parse raw data into intermediate internal cifs_fattr struct
Step 3: set fattr uniqueid which is later used for inode number. This
        can sometime be done from raw data
Step 4: tweak fattr according to mount options (file_mode, acl to mode
        bits, uid, gid, etc)
Step 5: update or create inode from final fattr struct

* add is_smb1_server() helper
* add is_inode_cache_good() helper
* move SMB1-backupcreds-getinfo-retry to separate func
  cifs_backup_query_path_info().
* move set-uniqueid code to separate func cifs_set_fattr_ino()
* don't clobber uniqueid from backup cred retry
* fix some probable corner cases memleaks

Signed-off-by: Aurelien Aptel <aaptel@suse.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/cifsglob.h
fs/cifs/inode.c

index 478b913..6bd917b 100644 (file)
@@ -1967,4 +1967,10 @@ extern struct smb_version_values smb302_values;
 #define ALT_SMB311_VERSION_STRING "3.11"
 extern struct smb_version_operations smb311_operations;
 extern struct smb_version_values smb311_values;
+
+static inline bool is_smb1_server(struct TCP_Server_Info *server)
+{
+       return strcmp(server->vals->version_string, SMB1_VERSION_STRING) == 0;
+}
+
 #endif /* _CIFS_GLOB_H */
index df93778..1fec2e7 100644 (file)
@@ -727,22 +727,138 @@ static __u64 simple_hashstr(const char *str)
        return hash;
 }
 
+/**
+ * cifs_backup_query_path_info - SMB1 fallback code to get ino
+ *
+ * Fallback code to get file metadata when we don't have access to
+ * @full_path (EACCESS) and have backup creds.
+ *
+ * @data will be set to search info result buffer
+ * @resp_buf will be set to cifs resp buf and needs to be freed with
+ * cifs_buf_release() when done with @data.
+ */
+static int
+cifs_backup_query_path_info(int xid,
+                           struct cifs_tcon *tcon,
+                           struct super_block *sb,
+                           const char *full_path,
+                           void **resp_buf,
+                           FILE_ALL_INFO **data)
+{
+       struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
+       struct cifs_search_info info = {0};
+       u16 flags;
+       int rc;
+
+       *resp_buf = NULL;
+       info.endOfSearch = false;
+       if (tcon->unix_ext)
+               info.info_level = SMB_FIND_FILE_UNIX;
+       else if ((tcon->ses->capabilities &
+                 tcon->ses->server->vals->cap_nt_find) == 0)
+               info.info_level = SMB_FIND_FILE_INFO_STANDARD;
+       else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)
+               info.info_level = SMB_FIND_FILE_ID_FULL_DIR_INFO;
+       else /* no srvino useful for fallback to some netapp */
+               info.info_level = SMB_FIND_FILE_DIRECTORY_INFO;
+
+       flags = CIFS_SEARCH_CLOSE_ALWAYS |
+               CIFS_SEARCH_CLOSE_AT_END |
+               CIFS_SEARCH_BACKUP_SEARCH;
+
+       rc = CIFSFindFirst(xid, tcon, full_path,
+                          cifs_sb, NULL, flags, &info, false);
+       if (rc)
+               return rc;
+
+       *resp_buf = (void *)info.ntwrk_buf_start;
+       *data = (FILE_ALL_INFO *)info.srch_entries_start;
+       return 0;
+}
+
+static void
+cifs_set_fattr_ino(int xid,
+                  struct cifs_tcon *tcon,
+                  struct super_block *sb,
+                  struct inode **inode,
+                  const char *full_path,
+                  FILE_ALL_INFO *data,
+                  struct cifs_fattr *fattr)
+{
+       struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
+       struct TCP_Server_Info *server = tcon->ses->server;
+       int rc;
+
+       if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)) {
+               if (*inode)
+                       fattr->cf_uniqueid = CIFS_I(*inode)->uniqueid;
+               else
+                       fattr->cf_uniqueid = iunique(sb, ROOT_I);
+               return;
+       }
+
+       /*
+        * If we have an inode pass a NULL tcon to ensure we don't
+        * make a round trip to the server. This only works for SMB2+.
+        */
+       rc = server->ops->get_srv_inum(xid,
+                                      *inode ? NULL : tcon,
+                                      cifs_sb, full_path,
+                                      &fattr->cf_uniqueid,
+                                      data);
+       if (rc) {
+               /*
+                * If that fails reuse existing ino or generate one
+                * and disable server ones
+                */
+               if (*inode)
+                       fattr->cf_uniqueid = CIFS_I(*inode)->uniqueid;
+               else {
+                       fattr->cf_uniqueid = iunique(sb, ROOT_I);
+                       cifs_autodisable_serverino(cifs_sb);
+               }
+               return;
+       }
+
+       /* If no errors, check for zero root inode (invalid) */
+       if (fattr->cf_uniqueid == 0 && strlen(full_path) == 0) {
+               cifs_dbg(FYI, "Invalid (0) inodenum\n");
+               if (*inode) {
+                       /* reuse */
+                       fattr->cf_uniqueid = CIFS_I(*inode)->uniqueid;
+               } else {
+                       /* make an ino by hashing the UNC */
+                       fattr->cf_flags |= CIFS_FATTR_FAKE_ROOT_INO;
+                       fattr->cf_uniqueid = simple_hashstr(tcon->treeName);
+               }
+       }
+}
+
+static inline bool is_inode_cache_good(struct inode *ino)
+{
+       return ino && CIFS_CACHE_READ(CIFS_I(ino)) && CIFS_I(ino)->time != 0;
+}
+
 int
-cifs_get_inode_info(struct inode **inode, const char *full_path,
-                   FILE_ALL_INFO *data, struct super_block *sb, int xid,
+cifs_get_inode_info(struct inode **inode,
+                   const char *full_path,
+                   FILE_ALL_INFO *in_data,
+                   struct super_block *sb, int xid,
                    const struct cifs_fid *fid)
 {
-       __u16 srchflgs;
-       int rc = 0, tmprc = ENOSYS;
+
        struct cifs_tcon *tcon;
        struct TCP_Server_Info *server;
        struct tcon_link *tlink;
        struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
-       char *buf = NULL;
        bool adjust_tz = false;
-       struct cifs_fattr fattr;
-       struct cifs_search_info *srchinf = NULL;
+       struct cifs_fattr fattr = {0};
        bool symlink = false;
+       FILE_ALL_INFO *data = in_data;
+       FILE_ALL_INFO *tmp_data = NULL;
+       void *smb1_backup_rsp_buf = NULL;
+       int rc = 0;
+       int tmprc = 0;
 
        tlink = cifs_sb_tlink(cifs_sb);
        if (IS_ERR(tlink))
@@ -750,142 +866,88 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
        tcon = tlink_tcon(tlink);
        server = tcon->ses->server;
 
-       cifs_dbg(FYI, "Getting info on %s\n", full_path);
+       /*
+        * 1. Fetch file metadata if not provided (data)
+        */
 
-       if ((data == NULL) && (*inode != NULL)) {
-               if (CIFS_CACHE_READ(CIFS_I(*inode)) &&
-                   CIFS_I(*inode)->time != 0) {
+       if (!data) {
+               if (is_inode_cache_good(*inode)) {
                        cifs_dbg(FYI, "No need to revalidate cached inode sizes\n");
-                       goto cgii_exit;
-               }
-       }
-
-       /* if inode info is not passed, get it from server */
-       if (data == NULL) {
-               if (!server->ops->query_path_info) {
-                       rc = -ENOSYS;
-                       goto cgii_exit;
+                       goto out;
                }
-               buf = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
-               if (buf == NULL) {
+               tmp_data = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
+               if (!tmp_data) {
                        rc = -ENOMEM;
-                       goto cgii_exit;
+                       goto out;
                }
-               data = (FILE_ALL_INFO *)buf;
-               rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path,
-                                                 data, &adjust_tz, &symlink);
+               rc = server->ops->query_path_info(xid, tcon, cifs_sb,
+                                                 full_path, tmp_data,
+                                                 &adjust_tz, &symlink);
+               data = tmp_data;
        }
 
-       if (!rc) {
-               cifs_all_info_to_fattr(&fattr, data, sb, adjust_tz,
-                                      symlink);
-       } else if (rc == -EREMOTE) {
+       /*
+        * 2. Convert it to internal cifs metadata (fattr)
+        */
+
+       switch (rc) {
+       case 0:
+               cifs_all_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;
-       } else if ((rc == -EACCES) && backup_cred(cifs_sb) &&
-                  (strcmp(server->vals->version_string, SMB1_VERSION_STRING)
-                     == 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
+                * perm errors, try again with backup flags if possible
+                *
+                * 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
                 */
+               if (backup_cred(cifs_sb) && is_smb1_server(server)) {
+                       /* for easier reading */
+                       FILE_DIRECTORY_INFO *fdi;
+                       SEARCH_ID_FULL_DIR_INFO *si;
+
+                       rc = cifs_backup_query_path_info(xid, tcon, sb,
+                                                        full_path,
+                                                        &smb1_backup_rsp_buf,
+                                                        &data);
+                       if (rc)
+                               goto out;
 
-               srchinf = kzalloc(sizeof(struct cifs_search_info),
-                                       GFP_KERNEL);
-               if (srchinf == NULL) {
-                       rc = -ENOMEM;
-                       goto cgii_exit;
-               }
+                       fdi = (FILE_DIRECTORY_INFO *)data;
+                       si = (SEARCH_ID_FULL_DIR_INFO *)data;
 
-               srchinf->endOfSearch = false;
-               if (tcon->unix_ext)
-                       srchinf->info_level = SMB_FIND_FILE_UNIX;
-               else if ((tcon->ses->capabilities &
-                        tcon->ses->server->vals->cap_nt_find) == 0)
-                       srchinf->info_level = SMB_FIND_FILE_INFO_STANDARD;
-               else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)
-                       srchinf->info_level = SMB_FIND_FILE_ID_FULL_DIR_INFO;
-               else /* no srvino useful for fallback to some netapp */
-                       srchinf->info_level = SMB_FIND_FILE_DIRECTORY_INFO;
-
-               srchflgs = CIFS_SEARCH_CLOSE_ALWAYS |
-                               CIFS_SEARCH_CLOSE_AT_END |
-                               CIFS_SEARCH_BACKUP_SEARCH;
-
-               rc = CIFSFindFirst(xid, tcon, full_path,
-                       cifs_sb, NULL, srchflgs, srchinf, false);
-               if (!rc) {
-                       data = (FILE_ALL_INFO *)srchinf->srch_entries_start;
+                       cifs_dir_info_to_fattr(&fattr, fdi, cifs_sb);
+                       fattr.cf_uniqueid = le64_to_cpu(si->UniqueId);
+                       /* uniqueid set, skip get inum step */
+                       goto handle_mnt_opt;
+               } else {
+                       /* nothing we can do, bail out */
+                       goto out;
+               }
+               break;
+       default:
+               cifs_dbg(FYI, "%s: unhandled err rc %d\n", __func__, rc);
+               goto out;
+       }
 
-                       cifs_dir_info_to_fattr(&fattr,
-                       (FILE_DIRECTORY_INFO *)data, cifs_sb);
-                       fattr.cf_uniqueid = le64_to_cpu(
-                       ((SEARCH_ID_FULL_DIR_INFO *)data)->UniqueId);
+       /*
+        * 3. Get or update inode number (fattr.cf_uniqueid)
+        */
 
-                       cifs_buf_release(srchinf->ntwrk_buf_start);
-               }
-               kfree(srchinf);
-               if (rc)
-                       goto cgii_exit;
-       } else
-               goto cgii_exit;
+       cifs_set_fattr_ino(xid, tcon, sb, inode, full_path, data, &fattr);
 
        /*
-        * If an inode wasn't passed in, then get the inode number
-        *
-        * Is an i_ino of zero legal? Can we use that to check if the server
-        * supports returning inode numbers?  Are there other sanity checks we
-        * can use to ensure that the server is really filling in that field?
+        * 4. Tweak fattr based on mount options
         */
-       if (*inode == NULL) {
-               if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) {
-                       if (server->ops->get_srv_inum)
-                               tmprc = server->ops->get_srv_inum(xid,
-                                                                 tcon, cifs_sb, full_path,
-                                                                 &fattr.cf_uniqueid, data);
-                       if (tmprc) {
-                               cifs_dbg(FYI, "GetSrvInodeNum rc %d\n",
-                                        tmprc);
-                               fattr.cf_uniqueid = iunique(sb, ROOT_I);
-                               cifs_autodisable_serverino(cifs_sb);
-                       } else if ((fattr.cf_uniqueid == 0) &&
-                                  strlen(full_path) == 0) {
-                               /* some servers ret bad root ino ie 0 */
-                               cifs_dbg(FYI, "Invalid (0) inodenum\n");
-                               fattr.cf_flags |=
-                                       CIFS_FATTR_FAKE_ROOT_INO;
-                               fattr.cf_uniqueid =
-                                       simple_hashstr(tcon->treeName);
-                       }
-               } else
-                       fattr.cf_uniqueid = iunique(sb, ROOT_I);
-       } else {
-               if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)
-                   && server->ops->get_srv_inum) {
-                       /*
-                        * Pass a NULL tcon to ensure we don't make a round
-                        * trip to the server. This only works for SMB2+.
-                        */
-                       tmprc = server->ops->get_srv_inum(xid,
-                               NULL, cifs_sb, full_path,
-                               &fattr.cf_uniqueid, data);
-                       if (tmprc)
-                               fattr.cf_uniqueid = CIFS_I(*inode)->uniqueid;
-                       else if ((fattr.cf_uniqueid == 0) &&
-                                       strlen(full_path) == 0) {
-                               /*
-                                * Reuse existing root inode num since
-                                * inum zero for root causes ls of . and .. to
-                                * not be returned
-                                */
-                               cifs_dbg(FYI, "Srv ret 0 inode num for root\n");
-                               fattr.cf_uniqueid = CIFS_I(*inode)->uniqueid;
-                       }
-               } else
-                       fattr.cf_uniqueid = CIFS_I(*inode)->uniqueid;
-       }
 
+handle_mnt_opt:
        /* query for SFU type info if supported and needed */
        if (fattr.cf_cifsattrs & ATTR_SYSTEM &&
            cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL) {
@@ -900,16 +962,15 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
                                       full_path, fid);
                if (rc) {
                        cifs_dbg(FYI, "%s: Get mode from SID failed. rc=%d\n",
-                               __func__, rc);
-                       goto cgii_exit;
+                                __func__, rc);
+                       goto out;
                }
        } else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_CIFS_ACL) {
                rc = cifs_acl_to_fattr(cifs_sb, &fattr, *inode, false,
-                                      full_path, fid);
-               if (rc) {
+                                      full_path, fid);         if (rc) {
                        cifs_dbg(FYI, "%s: Getting ACL failed with error: %d\n",
                                 __func__, rc);
-                       goto cgii_exit;
+                       goto out;
                }
        }
 
@@ -925,6 +986,10 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
                        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)
@@ -937,7 +1002,7 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
                    CIFS_I(*inode)->uniqueid != fattr.cf_uniqueid)) {
                        CIFS_I(*inode)->time = 0; /* force reval */
                        rc = -ESTALE;
-                       goto cgii_exit;
+                       goto out;
                }
 
                /* if filetype is different, return error */
@@ -945,18 +1010,15 @@ cifs_get_inode_info(struct inode **inode, const char *full_path,
                    (fattr.cf_mode & S_IFMT))) {
                        CIFS_I(*inode)->time = 0; /* force reval */
                        rc = -ESTALE;
-                       goto cgii_exit;
+                       goto out;
                }
 
                cifs_fattr_to_inode(*inode, &fattr);
        }
-
-cgii_exit:
-       if ((*inode) && ((*inode)->i_ino == 0))
-               cifs_dbg(FYI, "inode number of zero returned\n");
-
-       kfree(buf);
+out:
+       cifs_buf_release(smb1_backup_rsp_buf);
        cifs_put_tlink(tlink);
+       kfree(tmp_data);
        return rc;
 }