|
| 1 | +cifs: do not skip link targets when an I/O fails |
| 2 | + |
| 3 | +jira LE-1907 |
| 4 | +Rebuild_History Non-Buildable kernel-4.18.0-553.16.1.el8_10 |
| 5 | +commit-author Paulo Alcantara < [email protected]> |
| 6 | +commit 5d7e282541fc91b831a5c4477c5d72881c623df9 |
| 7 | +Empty-Commit: Cherry-Pick Conflicts during history rebuild. |
| 8 | +Will be included in final tarball splat. Ref for failed cherry-pick at: |
| 9 | +ciq/ciq_backports/kernel-4.18.0-553.16.1.el8_10/5d7e2825.failed |
| 10 | + |
| 11 | +When I/O fails in one of the currently connected DFS targets, retry it |
| 12 | +from other targets as specified in MS-DFSC "3.1.5.2 I/O Operation to |
| 13 | ++Target Fails with an Error Other Than STATUS_PATH_NOT_COVERED." |
| 14 | + |
| 15 | + Signed-off-by: Paulo Alcantara (SUSE) < [email protected]> |
| 16 | + |
| 17 | + Signed-off-by: Steve French < [email protected]> |
| 18 | +(cherry picked from commit 5d7e282541fc91b831a5c4477c5d72881c623df9) |
| 19 | + Signed-off-by: Jonathan Maple < [email protected]> |
| 20 | + |
| 21 | +# Conflicts: |
| 22 | +# fs/cifs/connect.c |
| 23 | +diff --cc fs/cifs/connect.c |
| 24 | +index 6e27e4672066,d6f8ccc7bfe2..000000000000 |
| 25 | +--- a/fs/cifs/connect.c |
| 26 | ++++ b/fs/cifs/connect.c |
| 27 | +@@@ -3559,117 -3491,223 +3559,327 @@@ int cifs_mount(struct cifs_sb_info *cif |
| 28 | + * to respond with PATH_NOT_COVERED to requests that include the prefix. |
| 29 | + */ |
| 30 | + if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) || |
| 31 | + - dfs_cache_find(mnt_ctx->xid, mnt_ctx->ses, cifs_sb->local_nls, cifs_remap(cifs_sb), |
| 32 | + - ctx->UNC + 1, NULL, root_tl)) { |
| 33 | + + dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), ctx->UNC + 1, NULL, |
| 34 | + + NULL)) { |
| 35 | + if (rc) |
| 36 | + - return rc; |
| 37 | + + goto error; |
| 38 | + /* Check if it is fully accessible and then mount it */ |
| 39 | + - rc = is_path_remote(mnt_ctx); |
| 40 | + + rc = is_path_remote(cifs_sb, ctx, xid, server, tcon); |
| 41 | + if (!rc) |
| 42 | +++<<<<<<< HEAD |
| 43 | + + goto out; |
| 44 | + + if (rc != -EREMOTE) |
| 45 | + + goto error; |
| 46 | + + } |
| 47 | + + |
| 48 | + + mount_put_conns(cifs_sb, xid, server, ses, tcon); |
| 49 | +++======= |
| 50 | ++ *isdfs = false; |
| 51 | ++ else if (rc != -EREMOTE) |
| 52 | ++ return rc; |
| 53 | ++ } |
| 54 | ++ return 0; |
| 55 | ++ } |
| 56 | ++ |
| 57 | ++ static int connect_dfs_target(struct mount_ctx *mnt_ctx, const char *full_path, |
| 58 | ++ const char *ref_path, struct dfs_cache_tgt_iterator *tit) |
| 59 | ++ { |
| 60 | ++ int rc; |
| 61 | ++ struct dfs_info3_param ref = {}; |
| 62 | ++ struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb; |
| 63 | ++ char *oldmnt = cifs_sb->ctx->mount_options; |
| 64 | ++ |
| 65 | ++ cifs_dbg(FYI, "%s: full_path=%s ref_path=%s target=%s\n", __func__, full_path, ref_path, |
| 66 | ++ dfs_cache_get_tgt_name(tit)); |
| 67 | ++ |
| 68 | ++ rc = dfs_cache_get_tgt_referral(ref_path, tit, &ref); |
| 69 | ++ if (rc) |
| 70 | ++ goto out; |
| 71 | ++ |
| 72 | ++ rc = expand_dfs_referral(mnt_ctx, full_path, &ref); |
| 73 | ++ if (rc) |
| 74 | ++ goto out; |
| 75 | ++ |
| 76 | ++ /* Connect to new target only if we were redirected (e.g. mount options changed) */ |
| 77 | ++ if (oldmnt != cifs_sb->ctx->mount_options) { |
| 78 | ++ mount_put_conns(mnt_ctx); |
| 79 | ++ rc = mount_get_dfs_conns(mnt_ctx); |
| 80 | ++ } |
| 81 | ++ if (!rc) { |
| 82 | ++ if (cifs_is_referral_server(mnt_ctx->tcon, &ref)) |
| 83 | ++ set_root_ses(mnt_ctx); |
| 84 | ++ rc = dfs_cache_update_tgthint(mnt_ctx->xid, mnt_ctx->root_ses, cifs_sb->local_nls, |
| 85 | ++ cifs_remap(cifs_sb), ref_path, tit); |
| 86 | ++ } |
| 87 | ++ |
| 88 | ++ out: |
| 89 | ++ free_dfs_info_param(&ref); |
| 90 | ++ return rc; |
| 91 | ++ } |
| 92 | ++ |
| 93 | ++ static int connect_dfs_root(struct mount_ctx *mnt_ctx, struct dfs_cache_tgt_list *root_tl) |
| 94 | ++ { |
| 95 | ++ int rc; |
| 96 | ++ char *full_path; |
| 97 | ++ struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb; |
| 98 | ++ struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; |
| 99 | ++ struct dfs_cache_tgt_iterator *tit; |
| 100 | ++ |
| 101 | ++ /* Put initial connections as they might be shared with other mounts. We need unique dfs |
| 102 | ++ * connections per mount to properly failover, so mount_get_dfs_conns() must be used from |
| 103 | ++ * now on. |
| 104 | ++ */ |
| 105 | ++ mount_put_conns(mnt_ctx); |
| 106 | ++ mount_get_dfs_conns(mnt_ctx); |
| 107 | ++ set_root_ses(mnt_ctx); |
| 108 | ++ |
| 109 | ++ full_path = build_unc_path_to_root(ctx, cifs_sb, true); |
| 110 | ++ if (IS_ERR(full_path)) |
| 111 | ++ return PTR_ERR(full_path); |
| 112 | ++ |
| 113 | ++ mnt_ctx->origin_fullpath = dfs_cache_canonical_path(ctx->UNC, cifs_sb->local_nls, |
| 114 | ++ cifs_remap(cifs_sb)); |
| 115 | ++ if (IS_ERR(mnt_ctx->origin_fullpath)) { |
| 116 | ++ rc = PTR_ERR(mnt_ctx->origin_fullpath); |
| 117 | ++ mnt_ctx->origin_fullpath = NULL; |
| 118 | ++ goto out; |
| 119 | ++ } |
| 120 | ++ |
| 121 | ++ /* Try all dfs root targets */ |
| 122 | ++ for (rc = -ENOENT, tit = dfs_cache_get_tgt_iterator(root_tl); |
| 123 | ++ tit; tit = dfs_cache_get_next_tgt(root_tl, tit)) { |
| 124 | ++ rc = connect_dfs_target(mnt_ctx, full_path, mnt_ctx->origin_fullpath + 1, tit); |
| 125 | ++ if (!rc) { |
| 126 | ++ mnt_ctx->leaf_fullpath = kstrdup(mnt_ctx->origin_fullpath, GFP_KERNEL); |
| 127 | ++ if (!mnt_ctx->leaf_fullpath) |
| 128 | ++ rc = -ENOMEM; |
| 129 | ++ break; |
| 130 | ++ } |
| 131 | ++ } |
| 132 | ++ |
| 133 | ++ out: |
| 134 | ++ kfree(full_path); |
| 135 | ++ return rc; |
| 136 | ++ } |
| 137 | ++ |
| 138 | ++ static int __follow_dfs_link(struct mount_ctx *mnt_ctx) |
| 139 | ++ { |
| 140 | ++ int rc; |
| 141 | ++ struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb; |
| 142 | ++ struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; |
| 143 | ++ char *full_path; |
| 144 | ++ struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl); |
| 145 | ++ struct dfs_cache_tgt_iterator *tit; |
| 146 | ++ |
| 147 | ++ full_path = build_unc_path_to_root(ctx, cifs_sb, true); |
| 148 | ++ if (IS_ERR(full_path)) |
| 149 | ++ return PTR_ERR(full_path); |
| 150 | ++ |
| 151 | ++ kfree(mnt_ctx->leaf_fullpath); |
| 152 | ++ mnt_ctx->leaf_fullpath = dfs_cache_canonical_path(full_path, cifs_sb->local_nls, |
| 153 | ++ cifs_remap(cifs_sb)); |
| 154 | ++ if (IS_ERR(mnt_ctx->leaf_fullpath)) { |
| 155 | ++ rc = PTR_ERR(mnt_ctx->leaf_fullpath); |
| 156 | ++ mnt_ctx->leaf_fullpath = NULL; |
| 157 | ++ goto out; |
| 158 | ++ } |
| 159 | ++ |
| 160 | ++ /* Get referral from dfs link */ |
| 161 | ++ rc = dfs_cache_find(mnt_ctx->xid, mnt_ctx->root_ses, cifs_sb->local_nls, |
| 162 | ++ cifs_remap(cifs_sb), mnt_ctx->leaf_fullpath + 1, NULL, &tl); |
| 163 | ++ if (rc) |
| 164 | ++ goto out; |
| 165 | ++ |
| 166 | ++ /* Try all dfs link targets. If an I/O fails from currently connected DFS target with an |
| 167 | ++ * error other than STATUS_PATH_NOT_COVERED (-EREMOTE), then retry it from other targets as |
| 168 | ++ * specified in MS-DFSC "3.1.5.2 I/O Operation to Target Fails with an Error Other Than |
| 169 | ++ * STATUS_PATH_NOT_COVERED." |
| 170 | ++ */ |
| 171 | ++ for (rc = -ENOENT, tit = dfs_cache_get_tgt_iterator(&tl); |
| 172 | ++ tit; tit = dfs_cache_get_next_tgt(&tl, tit)) { |
| 173 | ++ rc = connect_dfs_target(mnt_ctx, full_path, mnt_ctx->leaf_fullpath + 1, tit); |
| 174 | ++ if (!rc) { |
| 175 | ++ rc = is_path_remote(mnt_ctx); |
| 176 | ++ if (!rc || rc == -EREMOTE) |
| 177 | ++ break; |
| 178 | ++ } |
| 179 | ++ } |
| 180 | ++ |
| 181 | ++ out: |
| 182 | ++ kfree(full_path); |
| 183 | ++ dfs_cache_free_tgts(&tl); |
| 184 | ++ return rc; |
| 185 | ++ } |
| 186 | ++ |
| 187 | ++ static int follow_dfs_link(struct mount_ctx *mnt_ctx) |
| 188 | ++ { |
| 189 | ++ int rc; |
| 190 | ++ struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb; |
| 191 | ++ struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; |
| 192 | ++ char *full_path; |
| 193 | ++ int num_links = 0; |
| 194 | ++ |
| 195 | ++ full_path = build_unc_path_to_root(ctx, cifs_sb, true); |
| 196 | ++ if (IS_ERR(full_path)) |
| 197 | ++ return PTR_ERR(full_path); |
| 198 | ++ |
| 199 | ++ kfree(mnt_ctx->origin_fullpath); |
| 200 | ++ mnt_ctx->origin_fullpath = dfs_cache_canonical_path(full_path, cifs_sb->local_nls, |
| 201 | ++ cifs_remap(cifs_sb)); |
| 202 | ++ kfree(full_path); |
| 203 | ++ |
| 204 | ++ if (IS_ERR(mnt_ctx->origin_fullpath)) { |
| 205 | ++ rc = PTR_ERR(mnt_ctx->origin_fullpath); |
| 206 | ++ mnt_ctx->origin_fullpath = NULL; |
| 207 | ++ return rc; |
| 208 | ++ } |
| 209 | ++ |
| 210 | ++ do { |
| 211 | ++ rc = __follow_dfs_link(mnt_ctx); |
| 212 | ++ if (!rc || rc != -EREMOTE) |
| 213 | ++ break; |
| 214 | ++ } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS); |
| 215 | ++ |
| 216 | ++ return rc; |
| 217 | ++ } |
| 218 | ++ |
| 219 | ++ /* Set up DFS referral paths for failover */ |
| 220 | ++ static void setup_server_referral_paths(struct mount_ctx *mnt_ctx) |
| 221 | ++ { |
| 222 | ++ struct TCP_Server_Info *server = mnt_ctx->server; |
| 223 | ++ |
| 224 | ++ server->origin_fullpath = mnt_ctx->origin_fullpath; |
| 225 | ++ server->leaf_fullpath = mnt_ctx->leaf_fullpath; |
| 226 | ++ server->current_fullpath = mnt_ctx->leaf_fullpath; |
| 227 | ++ mnt_ctx->origin_fullpath = mnt_ctx->leaf_fullpath = NULL; |
| 228 | ++ } |
| 229 | ++ |
| 230 | ++ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx) |
| 231 | ++ { |
| 232 | ++ int rc; |
| 233 | ++ struct mount_ctx mnt_ctx = { .cifs_sb = cifs_sb, .fs_ctx = ctx, }; |
| 234 | ++ struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl); |
| 235 | ++ bool isdfs; |
| 236 | ++ |
| 237 | ++ rc = is_dfs_mount(&mnt_ctx, &isdfs, &tl); |
| 238 | ++ if (rc) |
| 239 | ++ goto error; |
| 240 | ++ if (!isdfs) |
| 241 | ++ goto out; |
| 242 | ++ |
| 243 | ++ uuid_gen(&mnt_ctx.mount_id); |
| 244 | ++ rc = connect_dfs_root(&mnt_ctx, &tl); |
| 245 | ++ dfs_cache_free_tgts(&tl); |
| 246 | ++ |
| 247 | ++ if (rc) |
| 248 | ++ goto error; |
| 249 | ++ |
| 250 | ++ rc = is_path_remote(&mnt_ctx); |
| 251 | ++ if (rc) |
| 252 | ++ rc = follow_dfs_link(&mnt_ctx); |
| 253 | ++ if (rc) |
| 254 | ++ goto error; |
| 255 | ++ |
| 256 | ++ setup_server_referral_paths(&mnt_ctx); |
| 257 | +++>>>>>>> 5d7e282541fc (cifs: do not skip link targets when an I/O fails) |
| 258 | + /* |
| 259 | + - * After reconnecting to a different server, unique ids won't match anymore, so we disable |
| 260 | + - * serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE). |
| 261 | + + * Ignore error check here because we may failover to other targets from cached a |
| 262 | + + * referral. |
| 263 | + + */ |
| 264 | + + (void)mount_get_dfs_conns(ctx, cifs_sb, &xid, &server, &ses, &tcon); |
| 265 | + + |
| 266 | + + /* Get path of DFS root */ |
| 267 | + + ref_path = build_unc_path_to_root(ctx, cifs_sb, false); |
| 268 | + + if (IS_ERR(ref_path)) { |
| 269 | + + rc = PTR_ERR(ref_path); |
| 270 | + + ref_path = NULL; |
| 271 | + + goto error; |
| 272 | + + } |
| 273 | + + |
| 274 | + + uuid_gen(&mount_id); |
| 275 | + + set_root_ses(cifs_sb, &mount_id, ses, &root_ses); |
| 276 | + + do { |
| 277 | + + /* Save full path of last DFS path we used to resolve final target server */ |
| 278 | + + kfree(full_path); |
| 279 | + + full_path = build_unc_path_to_root(ctx, cifs_sb, !!count); |
| 280 | + + if (IS_ERR(full_path)) { |
| 281 | + + rc = PTR_ERR(full_path); |
| 282 | + + full_path = NULL; |
| 283 | + + break; |
| 284 | + + } |
| 285 | + + /* Chase referral */ |
| 286 | + + oldmnt = cifs_sb->ctx->mount_options; |
| 287 | + + rc = expand_dfs_referral(xid, root_ses, ctx, cifs_sb, ref_path + 1); |
| 288 | + + if (rc) |
| 289 | + + break; |
| 290 | + + /* Connect to new DFS target only if we were redirected */ |
| 291 | + + if (oldmnt != cifs_sb->ctx->mount_options) { |
| 292 | + + mount_put_conns(cifs_sb, xid, server, ses, tcon); |
| 293 | + + rc = mount_get_dfs_conns(ctx, cifs_sb, &xid, &server, &ses, &tcon); |
| 294 | + + } |
| 295 | + + if (rc && !server && !ses) { |
| 296 | + + /* Failed to connect. Try to connect to other targets in the referral. */ |
| 297 | + + rc = do_dfs_failover(ref_path + 1, full_path, cifs_sb, ctx, root_ses, &xid, |
| 298 | + + &server, &ses, &tcon); |
| 299 | + + } |
| 300 | + + if (rc == -EACCES || rc == -EOPNOTSUPP || !server || !ses) |
| 301 | + + break; |
| 302 | + + if (!tcon) |
| 303 | + + continue; |
| 304 | + + |
| 305 | + + /* Make sure that requests go through new root servers */ |
| 306 | + + rc = is_referral_server(ref_path + 1, cifs_sb, tcon, &ref_server); |
| 307 | + + if (rc) |
| 308 | + + break; |
| 309 | + + if (ref_server) |
| 310 | + + set_root_ses(cifs_sb, &mount_id, ses, &root_ses); |
| 311 | + + |
| 312 | + + /* Get next dfs path and then continue chasing them if -EREMOTE */ |
| 313 | + + rc = next_dfs_prepath(cifs_sb, ctx, xid, server, tcon, &ref_path); |
| 314 | + + /* Prevent recursion on broken link referrals */ |
| 315 | + + if (rc == -EREMOTE && ++count > MAX_NESTED_LINKS) |
| 316 | + + rc = -ELOOP; |
| 317 | + + } while (rc == -EREMOTE); |
| 318 | + + |
| 319 | + + if (rc || !tcon) { |
| 320 | + + rc = rc ? rc : -ENOENT; |
| 321 | + + goto error; |
| 322 | + + } |
| 323 | + + |
| 324 | + + kfree(ref_path); |
| 325 | + + /* |
| 326 | + + * Store DFS full path in both superblock and tree connect structures. |
| 327 | + + * |
| 328 | + + * For DFS root mounts, the prefix path (cifs_sb->prepath) is preserved during reconnect so |
| 329 | + + * only the root path is set in cifs_sb->origin_fullpath and tcon->dfs_path. And for DFS |
| 330 | + + * links, the prefix path is included in both and may be changed during reconnect. See |
| 331 | + + * cifs_tree_connect(). |
| 332 | + + */ |
| 333 | + + ref_path = dfs_cache_canonical_path(full_path, cifs_sb->local_nls, cifs_remap(cifs_sb)); |
| 334 | + + kfree(full_path); |
| 335 | + + full_path = NULL; |
| 336 | + + |
| 337 | + + if (IS_ERR(ref_path)) { |
| 338 | + + rc = PTR_ERR(ref_path); |
| 339 | + + ref_path = NULL; |
| 340 | + + goto error; |
| 341 | + + } |
| 342 | + + cifs_sb->origin_fullpath = ref_path; |
| 343 | + + |
| 344 | + + ref_path = kstrdup(cifs_sb->origin_fullpath, GFP_KERNEL); |
| 345 | + + if (!ref_path) { |
| 346 | + + rc = -ENOMEM; |
| 347 | + + goto error; |
| 348 | + + } |
| 349 | + + spin_lock(&cifs_tcp_ses_lock); |
| 350 | + + tcon->dfs_path = ref_path; |
| 351 | + + ref_path = NULL; |
| 352 | + + spin_unlock(&cifs_tcp_ses_lock); |
| 353 | + + |
| 354 | + + /* |
| 355 | + + * After reconnecting to a different server, unique ids won't |
| 356 | + + * match anymore, so we disable serverino. This prevents |
| 357 | + + * dentry revalidation to think the dentry are stale (ESTALE). |
| 358 | + */ |
| 359 | + cifs_autodisable_serverino(cifs_sb); |
| 360 | + /* |
| 361 | +* Unmerged path fs/cifs/connect.c |
0 commit comments