Commit ce04127c authored by Paulo Alcantara's avatar Paulo Alcantara Committed by Steve French

smb: client: ensure to try all targets when finding nested links

With current implementation, when a nested DFS link is found during
mount(2), the client follows the referral and then try to connect to
all of its targets.  If all targets failed, the client bails out
rather than retrying remaining targets from previous referral.

Fix this by stacking all referrals and targets so the client can retry
remaining targets from previous referrals in case all targets of
current referral have failed.

Thanks to samba, this can be easily tested like below

* Run the following under dfs folder in samba server

  $ ln -s "msdfs:srv\\bad-share" link1
  $ ln -s "msdfs:srv\\dfs\\link1,srv\\good-share" link0

* Before patch

  $ mount.cifs //srv/dfs/link0 /mnt -o ...
  mount error(2): No such file or directory
  Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)...

* After patch

  $ mount.cifs //srv/dfs/link0 /mnt -o ...
  # ls /mnt
  bar  fileshare1  sub
Signed-off-by: default avatarPaulo Alcantara (SUSE) <pc@manguebit.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 3fea12f3
...@@ -1721,11 +1721,23 @@ struct cifs_mount_ctx { ...@@ -1721,11 +1721,23 @@ struct cifs_mount_ctx {
struct list_head dfs_ses_list; struct list_head dfs_ses_list;
}; };
static inline void __free_dfs_info_param(struct dfs_info3_param *param)
{
kfree(param->path_name);
kfree(param->node_name);
}
static inline void free_dfs_info_param(struct dfs_info3_param *param) static inline void free_dfs_info_param(struct dfs_info3_param *param)
{
if (param)
__free_dfs_info_param(param);
}
static inline void zfree_dfs_info_param(struct dfs_info3_param *param)
{ {
if (param) { if (param) {
kfree(param->path_name); __free_dfs_info_param(param);
kfree(param->node_name); memset(param, 0, sizeof(*param));
} }
} }
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
* Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de> * Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
*/ */
#include <linux/namei.h>
#include "cifsproto.h" #include "cifsproto.h"
#include "cifs_debug.h" #include "cifs_debug.h"
#include "dns_resolve.h" #include "dns_resolve.h"
...@@ -96,51 +95,134 @@ static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx) ...@@ -96,51 +95,134 @@ static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
return 0; return 0;
} }
static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path, static inline int parse_dfs_target(struct smb3_fs_context *ctx,
const struct dfs_cache_tgt_iterator *tit) struct dfs_ref_walk *rw,
struct dfs_info3_param *tgt)
{
int rc;
const char *fpath = ref_walk_fpath(rw) + 1;
rc = ref_walk_get_tgt(rw, tgt);
if (!rc)
rc = dfs_parse_target_referral(fpath, tgt, ctx);
return rc;
}
static int set_ref_paths(struct cifs_mount_ctx *mnt_ctx,
struct dfs_info3_param *tgt,
struct dfs_ref_walk *rw)
{ {
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
struct dfs_info3_param ref = {}; struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
bool is_refsrv; char *ref_path, *full_path;
int rc, rc2; int rc;
rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref); full_path = smb3_fs_context_fullpath(ctx, CIFS_DIR_SEP(cifs_sb));
if (rc) 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; return rc;
}
ref_walk_path(rw) = ref_path;
ref_walk_fpath(rw) = full_path;
return 0;
}
rc = dfs_parse_target_referral(full_path + 1, &ref, ctx); static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
if (rc) struct dfs_ref_walk *rw)
goto out; {
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
struct dfs_info3_param tgt = {};
bool is_refsrv;
int rc = -ENOENT;
cifs_mount_put_conns(mnt_ctx); again:
rc = get_session(mnt_ctx, ref_path); do {
if (rc) if (ref_walk_empty(rw)) {
goto out; rc = dfs_get_referral(mnt_ctx, ref_walk_path(rw) + 1,
NULL, ref_walk_tl(rw));
if (rc) {
rc = cifs_mount_get_tcon(mnt_ctx);
if (!rc)
rc = cifs_is_path_remote(mnt_ctx);
continue;
}
if (!ref_walk_num_tgts(rw)) {
rc = -ENOENT;
continue;
}
}
is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER); while (ref_walk_next_tgt(rw)) {
rc = parse_dfs_target(ctx, rw, &tgt);
if (rc)
continue;
rc = -EREMOTE; cifs_mount_put_conns(mnt_ctx);
if (ref.flags & DFSREF_STORAGE_SERVER) { rc = get_session(mnt_ctx, ref_walk_path(rw));
rc = cifs_mount_get_tcon(mnt_ctx); if (rc)
if (rc) continue;
goto out;
/* some servers may not advertise referral capability under ref.flags */ is_refsrv = tgt.server_type == DFS_TYPE_ROOT ||
is_refsrv |= is_tcon_dfs(mnt_ctx->tcon); DFS_INTERLINK(tgt.flags);
ref_walk_set_tgt_hint(rw);
rc = cifs_is_path_remote(mnt_ctx); if (tgt.flags & DFSREF_STORAGE_SERVER) {
} rc = cifs_mount_get_tcon(mnt_ctx);
if (!rc)
rc = cifs_is_path_remote(mnt_ctx);
if (!rc)
break;
if (rc != -EREMOTE)
continue;
}
dfs_cache_noreq_update_tgthint(ref_path + 1, tit); if (is_refsrv) {
rc = add_root_smb_session(mnt_ctx);
if (rc)
goto out;
}
if (rc == -EREMOTE && is_refsrv) { rc = ref_walk_advance(rw);
rc2 = add_root_smb_session(mnt_ctx); if (!rc) {
if (rc2) rc = set_ref_paths(mnt_ctx, &tgt, rw);
rc = rc2; if (!rc) {
} rc = -EREMOTE;
goto again;
}
}
if (rc != -ELOOP)
goto out;
}
} while (rc && ref_walk_descend(rw));
out: out:
free_dfs_info_param(&ref); free_dfs_info_param(&tgt);
return rc;
}
static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx)
{
struct dfs_ref_walk *rw;
int rc;
rw = ref_walk_alloc();
if (IS_ERR(rw))
return PTR_ERR(rw);
ref_walk_init(rw);
rc = set_ref_paths(mnt_ctx, NULL, rw);
if (!rc)
rc = __dfs_referral_walk(mnt_ctx, rw);
ref_walk_free(rw);
return rc; return rc;
} }
...@@ -148,105 +230,36 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx) ...@@ -148,105 +230,36 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
{ {
struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb; struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx; struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
char *ref_path = NULL, *full_path = NULL;
struct dfs_cache_tgt_iterator *tit;
struct cifs_tcon *tcon; struct cifs_tcon *tcon;
char *origin_fullpath = NULL; char *origin_fullpath;
char sep = CIFS_DIR_SEP(cifs_sb);
int num_links = 0;
int rc; int rc;
ref_path = dfs_get_path(cifs_sb, ctx->UNC); origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
if (IS_ERR(ref_path)) if (IS_ERR(origin_fullpath))
return PTR_ERR(ref_path); return PTR_ERR(origin_fullpath);
full_path = smb3_fs_context_fullpath(ctx, sep); rc = dfs_referral_walk(mnt_ctx);
if (IS_ERR(full_path)) { if (rc)
rc = PTR_ERR(full_path);
full_path = NULL;
goto out; goto out;
}
origin_fullpath = kstrdup(full_path, GFP_KERNEL); tcon = mnt_ctx->tcon;
if (!origin_fullpath) { spin_lock(&tcon->tc_lock);
rc = -ENOMEM; if (!tcon->origin_fullpath) {
goto out; tcon->origin_fullpath = origin_fullpath;
origin_fullpath = NULL;
} }
spin_unlock(&tcon->tc_lock);
do { if (list_empty(&tcon->dfs_ses_list)) {
DFS_CACHE_TGT_LIST(tl); list_replace_init(&mnt_ctx->dfs_ses_list, &tcon->dfs_ses_list);
queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
rc = dfs_get_referral(mnt_ctx, ref_path + 1, NULL, &tl); dfs_cache_get_ttl() * HZ);
if (rc) { } else {
rc = cifs_mount_get_tcon(mnt_ctx); dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
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);
if (!rc) {
tcon = mnt_ctx->tcon;
spin_lock(&tcon->tc_lock);
if (!tcon->origin_fullpath) {
tcon->origin_fullpath = origin_fullpath;
origin_fullpath = NULL;
}
spin_unlock(&tcon->tc_lock);
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);
}
} }
out: out:
kfree(origin_fullpath); kfree(origin_fullpath);
kfree(ref_path);
kfree(full_path);
return rc; return rc;
} }
......
...@@ -9,6 +9,110 @@ ...@@ -9,6 +9,110 @@
#include "cifsglob.h" #include "cifsglob.h"
#include "fs_context.h" #include "fs_context.h"
#include "cifs_unicode.h" #include "cifs_unicode.h"
#include <linux/namei.h>
#define DFS_INTERLINK(v) \
(((v) & DFSREF_REFERRAL_SERVER) && !((v) & DFSREF_STORAGE_SERVER))
struct dfs_ref {
char *path;
char *full_path;
struct dfs_cache_tgt_list tl;
struct dfs_cache_tgt_iterator *tit;
};
struct dfs_ref_walk {
struct dfs_ref *ref;
struct dfs_ref refs[MAX_NESTED_LINKS];
};
#define ref_walk_start(w) ((w)->refs)
#define ref_walk_end(w) (&(w)->refs[ARRAY_SIZE((w)->refs) - 1])
#define ref_walk_cur(w) ((w)->ref)
#define ref_walk_descend(w) (--ref_walk_cur(w) >= ref_walk_start(w))
#define ref_walk_tit(w) (ref_walk_cur(w)->tit)
#define ref_walk_empty(w) (!ref_walk_tit(w))
#define ref_walk_path(w) (ref_walk_cur(w)->path)
#define ref_walk_fpath(w) (ref_walk_cur(w)->full_path)
#define ref_walk_tl(w) (&ref_walk_cur(w)->tl)
static inline struct dfs_ref_walk *ref_walk_alloc(void)
{
struct dfs_ref_walk *rw;
rw = kmalloc(sizeof(*rw), GFP_KERNEL);
if (!rw)
return ERR_PTR(-ENOMEM);
return rw;
}
static inline void ref_walk_init(struct dfs_ref_walk *rw)
{
memset(rw, 0, sizeof(*rw));
ref_walk_cur(rw) = ref_walk_start(rw);
}
static inline void __ref_walk_free(struct dfs_ref *ref)
{
kfree(ref->path);
kfree(ref->full_path);
dfs_cache_free_tgts(&ref->tl);
memset(ref, 0, sizeof(*ref));
}
static inline void ref_walk_free(struct dfs_ref_walk *rw)
{
struct dfs_ref *ref = ref_walk_start(rw);
for (; ref <= ref_walk_end(rw); ref++)
__ref_walk_free(ref);
kfree(rw);
}
static inline int ref_walk_advance(struct dfs_ref_walk *rw)
{
struct dfs_ref *ref = ref_walk_cur(rw) + 1;
if (ref > ref_walk_end(rw))
return -ELOOP;
__ref_walk_free(ref);
ref_walk_cur(rw) = ref;
return 0;
}
static inline struct dfs_cache_tgt_iterator *
ref_walk_next_tgt(struct dfs_ref_walk *rw)
{
struct dfs_cache_tgt_iterator *tit;
struct dfs_ref *ref = ref_walk_cur(rw);
if (!ref->tit)
tit = dfs_cache_get_tgt_iterator(&ref->tl);
else
tit = dfs_cache_get_next_tgt(&ref->tl, ref->tit);
ref->tit = tit;
return tit;
}
static inline int ref_walk_get_tgt(struct dfs_ref_walk *rw,
struct dfs_info3_param *tgt)
{
zfree_dfs_info_param(tgt);
return dfs_cache_get_tgt_referral(ref_walk_path(rw) + 1,
ref_walk_tit(rw), tgt);
}
static inline int ref_walk_num_tgts(struct dfs_ref_walk *rw)
{
return dfs_cache_get_nr_tgts(ref_walk_tl(rw));
}
static inline void ref_walk_set_tgt_hint(struct dfs_ref_walk *rw)
{
dfs_cache_noreq_update_tgthint(ref_walk_path(rw) + 1,
ref_walk_tit(rw));
}
struct dfs_root_ses { struct dfs_root_ses {
struct list_head list; struct list_head list;
......
...@@ -29,8 +29,6 @@ ...@@ -29,8 +29,6 @@
#define CACHE_MIN_TTL 120 /* 2 minutes */ #define CACHE_MIN_TTL 120 /* 2 minutes */
#define CACHE_DEFAULT_TTL 300 /* 5 minutes */ #define CACHE_DEFAULT_TTL 300 /* 5 minutes */
#define IS_DFS_INTERLINK(v) (((v) & DFSREF_REFERRAL_SERVER) && !((v) & DFSREF_STORAGE_SERVER))
struct cache_dfs_tgt { struct cache_dfs_tgt {
char *name; char *name;
int path_consumed; int path_consumed;
...@@ -174,7 +172,7 @@ static int dfscache_proc_show(struct seq_file *m, void *v) ...@@ -174,7 +172,7 @@ static int dfscache_proc_show(struct seq_file *m, void *v)
"cache entry: path=%s,type=%s,ttl=%d,etime=%ld,hdr_flags=0x%x,ref_flags=0x%x,interlink=%s,path_consumed=%d,expired=%s\n", "cache entry: path=%s,type=%s,ttl=%d,etime=%ld,hdr_flags=0x%x,ref_flags=0x%x,interlink=%s,path_consumed=%d,expired=%s\n",
ce->path, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->path, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link",
ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags, ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags,
IS_DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no", DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
ce->path_consumed, cache_entry_expired(ce) ? "yes" : "no"); ce->path_consumed, cache_entry_expired(ce) ? "yes" : "no");
list_for_each_entry(t, &ce->tlist, list) { list_for_each_entry(t, &ce->tlist, list) {
...@@ -243,7 +241,7 @@ static inline void dump_ce(const struct cache_entry *ce) ...@@ -243,7 +241,7 @@ static inline void dump_ce(const struct cache_entry *ce)
ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl,
ce->etime.tv_nsec, ce->etime.tv_nsec,
ce->hdr_flags, ce->ref_flags, ce->hdr_flags, ce->ref_flags,
IS_DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no", DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
ce->path_consumed, ce->path_consumed,
cache_entry_expired(ce) ? "yes" : "no"); cache_entry_expired(ce) ? "yes" : "no");
dump_tgts(ce); dump_tgts(ce);
......
...@@ -55,8 +55,8 @@ static inline struct dfs_cache_tgt_iterator * ...@@ -55,8 +55,8 @@ static inline struct dfs_cache_tgt_iterator *
dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl, dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl,
struct dfs_cache_tgt_iterator *it) struct dfs_cache_tgt_iterator *it)
{ {
if (!tl || list_empty(&tl->tl_list) || !it || if (!tl || !tl->tl_numtgts || list_empty(&tl->tl_list) ||
list_is_last(&it->it_list, &tl->tl_list)) !it || list_is_last(&it->it_list, &tl->tl_list))
return NULL; return NULL;
return list_next_entry(it, it_list); return list_next_entry(it, it_list);
} }
...@@ -75,7 +75,7 @@ static inline void dfs_cache_free_tgts(struct dfs_cache_tgt_list *tl) ...@@ -75,7 +75,7 @@ static inline void dfs_cache_free_tgts(struct dfs_cache_tgt_list *tl)
{ {
struct dfs_cache_tgt_iterator *it, *nit; struct dfs_cache_tgt_iterator *it, *nit;
if (!tl || list_empty(&tl->tl_list)) if (!tl || !tl->tl_numtgts || list_empty(&tl->tl_list))
return; return;
list_for_each_entry_safe(it, nit, &tl->tl_list, it_list) { list_for_each_entry_safe(it, nit, &tl->tl_list, it_list) {
list_del(&it->it_list); list_del(&it->it_list);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment