cifs: introduce helper for finding referral server to improve DFS target resolution
[platform/kernel/linux-starfive.git] / fs / cifs / connect.c
index 1a6d6e1..b2447ce 100644 (file)
@@ -3333,6 +3333,33 @@ out:
        return rc;
 }
 
+/* Check if resolved targets can handle any DFS referrals */
+static int is_referral_server(const char *ref_path, struct cifs_tcon *tcon, bool *ref_server)
+{
+       int rc;
+       struct dfs_info3_param ref = {0};
+
+       if (is_tcon_dfs(tcon)) {
+               *ref_server = true;
+       } else {
+               cifs_dbg(FYI, "%s: ref_path=%s\n", __func__, ref_path);
+
+               rc = dfs_cache_noreq_find(ref_path, &ref, NULL);
+               if (rc) {
+                       cifs_dbg(VFS, "%s: dfs_cache_noreq_find: failed (rc=%d)\n", __func__, rc);
+                       return rc;
+               }
+               cifs_dbg(FYI, "%s: ref.flags=0x%x\n", __func__, ref.flags);
+               /*
+                * Check if all targets are capable of handling DFS referrals as per
+                * MS-DFSC 2.2.4 RESP_GET_DFS_REFERRAL.
+                */
+               *ref_server = !!(ref.flags & DFSREF_REFERRAL_SERVER);
+               free_dfs_info_param(&ref);
+       }
+       return 0;
+}
+
 int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
 {
        int rc = 0;
@@ -3344,6 +3371,7 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
        char *ref_path = NULL, *full_path = NULL;
        char *oldmnt = NULL;
        char *mntdata = NULL;
+       bool ref_server = false;
 
        rc = mount_get_conns(ctx, cifs_sb, &xid, &server, &ses, &tcon);
        /*
@@ -3409,11 +3437,16 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
                        break;
                if (!tcon)
                        continue;
+
                /* Make sure that requests go through new root servers */
-               if (is_tcon_dfs(tcon)) {
+               rc = is_referral_server(ref_path + 1, tcon, &ref_server);
+               if (rc)
+                       break;
+               if (ref_server) {
                        put_root_ses(root_ses);
                        set_root_ses(cifs_sb, ses, &root_ses);
                }
+
                /* Get next dfs path and then continue chasing them if -EREMOTE */
                rc = next_dfs_prepath(cifs_sb, ctx, xid, server, tcon, &ref_path);
                /* Prevent recursion on broken link referrals */