diff options
Diffstat (limited to 'fs/smb/client/dfs.c')
| -rw-r--r-- | fs/smb/client/dfs.c | 511 |
1 files changed, 191 insertions, 320 deletions
diff --git a/fs/smb/client/dfs.c b/fs/smb/client/dfs.c index ee772c3d9f00..f65a8a90ba27 100644 --- a/fs/smb/client/dfs.c +++ b/fs/smb/client/dfs.c @@ -3,13 +3,14 @@ * Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de> */ -#include <linux/namei.h> #include "cifsproto.h" #include "cifs_debug.h" #include "dns_resolve.h" #include "fs_context.h" #include "dfs.h" +#define DFS_DOM(ctx) (ctx->dfs_root_ses ? ctx->dfs_root_ses->dns_dom : NULL) + /** * dfs_parse_target_referral - set fs context for dfs target referral * @@ -47,8 +48,8 @@ int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_para if (rc) goto out; - rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL); - + rc = dns_resolve_unc(DFS_DOM(ctx), path, + (struct sockaddr *)&ctx->dstaddr); out: kfree(path); return rc; @@ -60,87 +61,140 @@ static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path) int rc; ctx->leaf_fullpath = (char *)full_path; + ctx->dns_dom = DFS_DOM(ctx); rc = cifs_mount_get_session(mnt_ctx); - ctx->leaf_fullpath = NULL; + ctx->leaf_fullpath = ctx->dns_dom = NULL; return rc; } /* - * Track individual DFS referral servers used by new DFS mount. - * - * On success, their lifetime will be shared by final tcon (dfs_ses_list). - * Otherwise, they will be put by dfs_put_root_smb_sessions() in cifs_mount(). + * Get an active reference of @ses so that next call to cifs_put_tcon() won't + * release it as any new DFS referrals must go through its IPC tcon. */ -static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx) +static void set_root_smb_session(struct cifs_mount_ctx *mnt_ctx) { struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; - struct dfs_root_ses *root_ses; struct cifs_ses *ses = mnt_ctx->ses; if (ses) { - root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL); - if (!root_ses) - return -ENOMEM; - - INIT_LIST_HEAD(&root_ses->list); - spin_lock(&cifs_tcp_ses_lock); cifs_smb_ses_inc_refcount(ses); spin_unlock(&cifs_tcp_ses_lock); - root_ses->ses = ses; - list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list); } - /* Select new DFS referral server so that new referrals go through it */ ctx->dfs_root_ses = ses; - return 0; } -static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path, - const struct dfs_cache_tgt_iterator *tit) +static inline int parse_dfs_target(struct smb3_fs_context *ctx, + struct dfs_ref_walk *rw, + struct dfs_info3_param *tgt) { - struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; - struct dfs_info3_param ref = {}; - bool is_refsrv; - int rc, rc2; + int rc; + const char *fpath = ref_walk_fpath(rw) + 1; - rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref); - if (rc) + rc = ref_walk_get_tgt(rw, tgt); + if (!rc) + rc = dfs_parse_target_referral(fpath, tgt, ctx); + return rc; +} + +static int setup_dfs_ref(struct dfs_info3_param *tgt, struct dfs_ref_walk *rw) +{ + struct cifs_sb_info *cifs_sb = rw->mnt_ctx->cifs_sb; + struct smb3_fs_context *ctx = rw->mnt_ctx->fs_ctx; + char *ref_path, *full_path; + int rc; + + set_root_smb_session(rw->mnt_ctx); + ref_walk_ses(rw) = ctx->dfs_root_ses; + + full_path = smb3_fs_context_fullpath(ctx, CIFS_DIR_SEP(cifs_sb)); + if (IS_ERR(full_path)) + return PTR_ERR(full_path); + + if (!tgt || (tgt->server_type == DFS_TYPE_LINK && + DFS_INTERLINK(tgt->flags))) + ref_path = dfs_get_path(cifs_sb, ctx->UNC); + else + ref_path = dfs_get_path(cifs_sb, full_path); + if (IS_ERR(ref_path)) { + rc = PTR_ERR(ref_path); + kfree(full_path); return rc; + } + ref_walk_path(rw) = ref_path; + ref_walk_fpath(rw) = full_path; - rc = dfs_parse_target_referral(full_path + 1, &ref, ctx); - if (rc) - goto out; + return dfs_get_referral(rw->mnt_ctx, + ref_walk_path(rw) + 1, + ref_walk_tl(rw)); +} - cifs_mount_put_conns(mnt_ctx); - rc = get_session(mnt_ctx, ref_path); - if (rc) - goto out; +static int __dfs_referral_walk(struct dfs_ref_walk *rw) +{ + struct smb3_fs_context *ctx = rw->mnt_ctx->fs_ctx; + struct cifs_mount_ctx *mnt_ctx = rw->mnt_ctx; + struct dfs_info3_param tgt = {}; + int rc = -ENOENT; - is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER); +again: + do { + ctx->dfs_root_ses = ref_walk_ses(rw); + while (ref_walk_next_tgt(rw)) { + rc = parse_dfs_target(ctx, rw, &tgt); + if (rc) + continue; - rc = -EREMOTE; - if (ref.flags & DFSREF_STORAGE_SERVER) { - rc = cifs_mount_get_tcon(mnt_ctx); - if (rc) - goto out; + cifs_mount_put_conns(mnt_ctx); + rc = get_session(mnt_ctx, ref_walk_path(rw)); + if (rc) + continue; - /* some servers may not advertise referral capability under ref.flags */ - is_refsrv |= is_tcon_dfs(mnt_ctx->tcon); + rc = cifs_mount_get_tcon(mnt_ctx); + if (rc) { + if (tgt.server_type == DFS_TYPE_LINK && + DFS_INTERLINK(tgt.flags)) + rc = -EREMOTE; + } else { + rc = cifs_is_path_remote(mnt_ctx); + if (!rc) { + ref_walk_set_tgt_hint(rw); + break; + } + } + if (rc == -EREMOTE) { + rc = ref_walk_advance(rw); + if (!rc) { + rc = setup_dfs_ref(&tgt, rw); + if (rc) + break; + ref_walk_mark_end(rw); + goto again; + } + } + } + } while (rc && ref_walk_descend(rw)); - rc = cifs_is_path_remote(mnt_ctx); - } + free_dfs_info_param(&tgt); + return rc; +} - dfs_cache_noreq_update_tgthint(ref_path + 1, tit); +static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx, + struct dfs_ref_walk **rw) +{ + int rc; - if (rc == -EREMOTE && is_refsrv) { - rc2 = add_root_smb_session(mnt_ctx); - if (rc2) - rc = rc2; + *rw = ref_walk_alloc(); + if (IS_ERR(*rw)) { + rc = PTR_ERR(*rw); + *rw = NULL; + return rc; } -out: - free_dfs_info_param(&ref); + ref_walk_init(*rw, mnt_ctx); + rc = setup_dfs_ref(NULL, *rw); + if (!rc) + rc = __dfs_referral_walk(*rw); return rc; } @@ -148,120 +202,80 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx) { struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb; struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; - char *ref_path = NULL, *full_path = NULL; - struct dfs_cache_tgt_iterator *tit; + struct dfs_ref_walk *rw = NULL; struct cifs_tcon *tcon; - char *origin_fullpath = NULL; - char sep = CIFS_DIR_SEP(cifs_sb); - int num_links = 0; + char *origin_fullpath; int rc; - ref_path = dfs_get_path(cifs_sb, ctx->UNC); - if (IS_ERR(ref_path)) - return PTR_ERR(ref_path); + origin_fullpath = dfs_get_path(cifs_sb, ctx->source); + if (IS_ERR(origin_fullpath)) + return PTR_ERR(origin_fullpath); - full_path = smb3_fs_context_fullpath(ctx, sep); - if (IS_ERR(full_path)) { - rc = PTR_ERR(full_path); - full_path = NULL; - goto out; + rc = dfs_referral_walk(mnt_ctx, &rw); + if (!rc) { + /* + * Prevent superblock from being created with any missing + * connections. + */ + if (WARN_ON(!mnt_ctx->server)) + rc = -EHOSTDOWN; + else if (WARN_ON(!mnt_ctx->ses)) + rc = -EACCES; + else if (WARN_ON(!mnt_ctx->tcon)) + rc = -ENOENT; } - - origin_fullpath = kstrdup(full_path, GFP_KERNEL); - if (!origin_fullpath) { - rc = -ENOMEM; + if (rc) goto out; - } - - do { - struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl); - - rc = dfs_get_referral(mnt_ctx, ref_path + 1, NULL, &tl); - if (rc) { - rc = cifs_mount_get_tcon(mnt_ctx); - if (!rc) - rc = cifs_is_path_remote(mnt_ctx); - break; - } - - tit = dfs_cache_get_tgt_iterator(&tl); - if (!tit) { - cifs_dbg(VFS, "%s: dfs referral (%s) with no targets\n", __func__, - ref_path + 1); - rc = -ENOENT; - dfs_cache_free_tgts(&tl); - break; - } - do { - rc = get_dfs_conn(mnt_ctx, ref_path, full_path, tit); - if (!rc) - break; - if (rc == -EREMOTE) { - if (++num_links > MAX_NESTED_LINKS) { - rc = -ELOOP; - break; - } - kfree(ref_path); - kfree(full_path); - ref_path = full_path = NULL; - - full_path = smb3_fs_context_fullpath(ctx, sep); - if (IS_ERR(full_path)) { - rc = PTR_ERR(full_path); - full_path = NULL; - } else { - ref_path = dfs_get_path(cifs_sb, full_path); - if (IS_ERR(ref_path)) { - rc = PTR_ERR(ref_path); - ref_path = NULL; - } - } - break; - } - } while ((tit = dfs_cache_get_next_tgt(&tl, tit))); - dfs_cache_free_tgts(&tl); - } while (rc == -EREMOTE); + tcon = mnt_ctx->tcon; + spin_lock(&tcon->tc_lock); + tcon->origin_fullpath = origin_fullpath; + origin_fullpath = NULL; + ref_walk_set_tcon(rw, tcon); + spin_unlock(&tcon->tc_lock); + queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work, + dfs_cache_get_ttl() * HZ); - if (!rc) { - tcon = mnt_ctx->tcon; +out: + kfree(origin_fullpath); + ref_walk_free(rw); + return rc; +} - spin_lock(&tcon->tc_lock); - if (!tcon->origin_fullpath) { - tcon->origin_fullpath = origin_fullpath; - origin_fullpath = NULL; - } - spin_unlock(&tcon->tc_lock); +/* + * If @ctx->dfs_automount, then update @ctx->dstaddr earlier with the DFS root + * server from where we'll start following any referrals. Otherwise rely on the + * value provided by mount(2) as the user might not have dns_resolver key set up + * and therefore failing to upcall to resolve UNC hostname under @ctx->source. + */ +static int update_fs_context_dstaddr(struct smb3_fs_context *ctx) +{ + struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr; + int rc = 0; - if (list_empty(&tcon->dfs_ses_list)) { - list_replace_init(&mnt_ctx->dfs_ses_list, - &tcon->dfs_ses_list); - queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work, - dfs_cache_get_ttl() * HZ); - } else { - dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list); - } + if (!ctx->nodfs && ctx->dfs_automount) { + rc = dns_resolve_unc(NULL, ctx->source, addr); + if (!rc) + cifs_set_port(addr, ctx->port); + ctx->dfs_automount = false; } - -out: - kfree(origin_fullpath); - kfree(ref_path); - kfree(full_path); return rc; } -int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs) +int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx) { struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; bool nodfs = ctx->nodfs; int rc; - *isdfs = false; + rc = update_fs_context_dstaddr(ctx); + if (rc) + return rc; + rc = get_session(mnt_ctx, NULL); if (rc) return rc; - ctx->dfs_root_ses = mnt_ctx->ses; /* * If called with 'nodfs' mount option, then skip DFS resolving. Otherwise unconditionally * try to get an DFS referral (even cached) to determine whether it is an DFS mount. @@ -270,7 +284,7 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs) * to respond with PATH_NOT_COVERED to requests that include the prefix. */ if (!nodfs) { - rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL); + rc = dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL); if (rc) { cifs_dbg(FYI, "%s: no dfs referral for %s: %d\n", __func__, ctx->UNC + 1, rc); @@ -285,51 +299,13 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs) return rc; } - *isdfs = true; - add_root_smb_session(mnt_ctx); - return __dfs_mount_share(mnt_ctx); -} - -/* Update dfs referral path of superblock */ -static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb, - const char *target) -{ - int rc = 0; - size_t len = strlen(target); - char *refpath, *npath; - - if (unlikely(len < 2 || *target != '\\')) - return -EINVAL; - - if (target[1] == '\\') { - len += 1; - refpath = kmalloc(len, GFP_KERNEL); - if (!refpath) - return -ENOMEM; - - scnprintf(refpath, len, "%s", target); - } else { - len += sizeof("\\"); - refpath = kmalloc(len, GFP_KERNEL); - if (!refpath) - return -ENOMEM; - - scnprintf(refpath, len, "\\%s", target); - } - - npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb)); - kfree(refpath); - - if (IS_ERR(npath)) { - rc = PTR_ERR(npath); - } else { - mutex_lock(&server->refpath_lock); - spin_lock(&server->srv_lock); - kfree(server->leaf_fullpath); - server->leaf_fullpath = npath; - spin_unlock(&server->srv_lock); - mutex_unlock(&server->refpath_lock); + if (!ctx->dfs_conn) { + ctx->dfs_conn = true; + cifs_mount_put_conns(mnt_ctx); + rc = get_session(mnt_ctx, NULL); } + if (!rc) + rc = __dfs_mount_share(mnt_ctx); return rc; } @@ -357,77 +333,22 @@ static int target_share_matches_server(struct TCP_Server_Info *server, char *sha return rc; } -static void __tree_connect_ipc(const unsigned int xid, char *tree, - struct cifs_sb_info *cifs_sb, - struct cifs_ses *ses) -{ - struct TCP_Server_Info *server = ses->server; - struct cifs_tcon *tcon = ses->tcon_ipc; - int rc; - - spin_lock(&ses->ses_lock); - spin_lock(&ses->chan_lock); - if (cifs_chan_needs_reconnect(ses, server) || - ses->ses_status != SES_GOOD) { - spin_unlock(&ses->chan_lock); - spin_unlock(&ses->ses_lock); - cifs_server_dbg(FYI, "%s: skipping ipc reconnect due to disconnected ses\n", - __func__); - return; - } - spin_unlock(&ses->chan_lock); - spin_unlock(&ses->ses_lock); - - cifs_server_lock(server); - scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname); - cifs_server_unlock(server); - - rc = server->ops->tree_connect(xid, ses, tree, tcon, - cifs_sb->local_nls); - cifs_server_dbg(FYI, "%s: tree_reconnect %s: %d\n", __func__, tree, rc); - spin_lock(&tcon->tc_lock); - if (rc) { - tcon->status = TID_NEED_TCON; - } else { - tcon->status = TID_GOOD; - tcon->need_reconnect = false; - } - spin_unlock(&tcon->tc_lock); -} - -static void tree_connect_ipc(const unsigned int xid, char *tree, - struct cifs_sb_info *cifs_sb, - struct cifs_tcon *tcon) -{ - struct cifs_ses *ses = tcon->ses; - - __tree_connect_ipc(xid, tree, cifs_sb, ses); - __tree_connect_ipc(xid, tree, cifs_sb, CIFS_DFS_ROOT_SES(ses)); -} - -static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, char *tree, bool islink, - struct dfs_cache_tgt_list *tl) +static int tree_connect_dfs_target(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + char *tree, bool islink, + struct dfs_cache_tgt_list *tl) { - int rc; + const struct smb_version_operations *ops = tcon->ses->server->ops; struct TCP_Server_Info *server = tcon->ses->server; - const struct smb_version_operations *ops = server->ops; - struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses); - char *share = NULL, *prefix = NULL; struct dfs_cache_tgt_iterator *tit; + char *share = NULL, *prefix = NULL; bool target_match; - - tit = dfs_cache_get_tgt_iterator(tl); - if (!tit) { - rc = -ENOENT; - goto out; - } + int rc = -ENOENT; /* Try to tree connect to all dfs targets */ - for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) { - const char *target = dfs_cache_get_tgt_name(tit); - struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl); - + for (tit = dfs_cache_get_tgt_iterator(tl); + tit; tit = dfs_cache_get_next_tgt(tl, tit)) { kfree(share); kfree(prefix); share = prefix = NULL; @@ -448,79 +369,26 @@ static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *t } dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit); - tree_connect_ipc(xid, tree, cifs_sb, tcon); - scnprintf(tree, MAX_TREE_SIZE, "\\%s", share); - if (!islink) { - rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls); - break; - } - - /* - * If no dfs referrals were returned from link target, then just do a TREE_CONNECT - * to it. Otherwise, cache the dfs referral and then mark current tcp ses for - * reconnect so either the demultiplex thread or the echo worker will reconnect to - * newly resolved target. - */ - if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target, - NULL, &ntl)) { - rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls); - if (rc) - continue; - + rc = ops->tree_connect(xid, tcon->ses, tree, + tcon, tcon->ses->local_nls); + if (islink && !rc && cifs_sb) rc = cifs_update_super_prepath(cifs_sb, prefix); - } else { - /* Target is another dfs share */ - rc = update_server_fullpath(server, cifs_sb, target); - dfs_cache_free_tgts(tl); - - if (!rc) { - rc = -EREMOTE; - list_replace_init(&ntl.tl_list, &tl->tl_list); - } else - dfs_cache_free_tgts(&ntl); - } break; } -out: kfree(share); kfree(prefix); - - return rc; -} - -static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, char *tree, bool islink, - struct dfs_cache_tgt_list *tl) -{ - int rc; - int num_links = 0; - struct TCP_Server_Info *server = tcon->ses->server; - char *old_fullpath = server->leaf_fullpath; - - do { - rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl); - if (!rc || rc != -EREMOTE) - break; - } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS); - /* - * If we couldn't tree connect to any targets from last referral path, then - * retry it from newly resolved dfs referral. - */ - if (rc && server->leaf_fullpath != old_fullpath) - cifs_signal_cifsd_for_reconnect(server, true); - dfs_cache_free_tgts(tl); return rc; } -int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc) +int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon) { int rc; struct TCP_Server_Info *server = tcon->ses->server; const struct smb_version_operations *ops = server->ops; - struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl); + DFS_CACHE_TGT_LIST(tl); struct cifs_sb_info *cifs_sb = NULL; struct super_block *sb = NULL; struct dfs_info3_param ref = {0}; @@ -528,6 +396,11 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru /* only send once per connect */ spin_lock(&tcon->tc_lock); + + /* if tcon is marked for needing reconnect, update state */ + if (tcon->need_reconnect) + tcon->status = TID_NEED_TCON; + if (tcon->status == TID_GOOD) { spin_unlock(&tcon->tc_lock); return 0; @@ -552,7 +425,8 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru cifs_server_lock(server); scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname); cifs_server_unlock(server); - rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc); + rc = ops->tree_connect(xid, tcon->ses, tree, + tcon, tcon->ses->local_nls); goto out; } @@ -560,14 +434,11 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru if (!IS_ERR(sb)) cifs_sb = CIFS_SB(sb); - /* - * Tree connect to last share in @tcon->tree_name whether dfs super or - * cached dfs referral was not found. - */ - if (!cifs_sb || !server->leaf_fullpath || + /* Tree connect to last share in @tcon->tree_name if no DFS referral */ + if (!server->leaf_fullpath || dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) { - rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, - cifs_sb ? cifs_sb->local_nls : nlsc); + rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, + tcon, tcon->ses->local_nls); goto out; } @@ -588,8 +459,8 @@ out: spin_lock(&tcon->tc_lock); if (tcon->status == TID_IN_TCON) tcon->status = TID_GOOD; - spin_unlock(&tcon->tc_lock); tcon->need_reconnect = false; + spin_unlock(&tcon->tc_lock); } return rc; |
