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

cifs: share dfs connections and supers

When matching DFS superblocks we can't rely on either the server's
address or tcon's UNC name from mount(2) as the existing servers and
tcons might be connected to somewhere else.  Instead, check if
superblock is dfs, and if so, match its original source pathname with
the new mount's source pathname.

For DFS connections, instead of checking server's address, match its
referral path as it could be connected to different targets.
Signed-off-by: default avatarPaulo Alcantara (SUSE) <pc@cjr.nz>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent a73a26d9
......@@ -372,6 +372,14 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
seq_printf(m, "\nIn Send: %d In MaxReq Wait: %d",
atomic_read(&server->in_send),
atomic_read(&server->num_waiters));
if (IS_ENABLED(CONFIG_CIFS_DFS_UPCALL)) {
if (server->origin_fullpath)
seq_printf(m, "\nDFS origin full path: %s",
server->origin_fullpath);
if (server->leaf_fullpath)
seq_printf(m, "\nDFS leaf full path: %s",
server->leaf_fullpath);
}
seq_printf(m, "\n\n\tSessions: ");
i = 0;
......
......@@ -738,8 +738,6 @@ struct TCP_Server_Info {
bool use_swn_dstaddr;
struct sockaddr_storage swn_dstaddr;
#endif
#ifdef CONFIG_CIFS_DFS_UPCALL
bool is_dfs_conn; /* if a dfs connection */
struct mutex refpath_lock; /* protects leaf_fullpath */
/*
* Canonical DFS full paths that were used to chase referrals in mount and reconnect.
......@@ -753,7 +751,6 @@ struct TCP_Server_Info {
* format: \\HOST\SHARE\[OPTIONAL PATH]
*/
char *origin_fullpath, *leaf_fullpath, *current_fullpath;
#endif
};
static inline bool is_smb1(struct TCP_Server_Info *server)
......@@ -1767,11 +1764,9 @@ struct cifs_mount_ctx {
struct TCP_Server_Info *server;
struct cifs_ses *ses;
struct cifs_tcon *tcon;
#ifdef CONFIG_CIFS_DFS_UPCALL
struct cifs_ses *root_ses;
uuid_t mount_id;
char *origin_fullpath, *leaf_fullpath;
#endif
};
static inline void free_dfs_info_param(struct dfs_info3_param *param)
......
......@@ -242,7 +242,9 @@ extern int cifs_read_page_from_socket(struct TCP_Server_Info *server,
unsigned int page_offset,
unsigned int to_read);
extern int cifs_setup_cifs_sb(struct cifs_sb_info *cifs_sb);
void cifs_mount_put_conns(struct cifs_mount_ctx *mnt_ctx);
int cifs_mount_get_session(struct cifs_mount_ctx *mnt_ctx);
int cifs_is_path_remote(struct cifs_mount_ctx *mnt_ctx);
int cifs_mount_get_tcon(struct cifs_mount_ctx *mnt_ctx);
extern int cifs_match_super(struct super_block *, void *);
extern int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx);
......
This diff is collapsed.
......@@ -3,6 +3,7 @@
* Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
*/
#include <linux/namei.h>
#include "cifsproto.h"
#include "cifs_debug.h"
#include "dns_resolve.h"
......@@ -52,3 +53,228 @@ int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_para
kfree(path);
return rc;
}
/*
* cifs_build_path_to_root returns full path to root when we do not have an
* existing connection (tcon)
*/
static char *build_unc_path_to_root(const struct smb3_fs_context *ctx,
const struct cifs_sb_info *cifs_sb, bool useppath)
{
char *full_path, *pos;
unsigned int pplen = useppath && ctx->prepath ? strlen(ctx->prepath) + 1 : 0;
unsigned int unc_len = strnlen(ctx->UNC, MAX_TREE_SIZE + 1);
if (unc_len > MAX_TREE_SIZE)
return ERR_PTR(-EINVAL);
full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);
if (full_path == NULL)
return ERR_PTR(-ENOMEM);
memcpy(full_path, ctx->UNC, unc_len);
pos = full_path + unc_len;
if (pplen) {
*pos = CIFS_DIR_SEP(cifs_sb);
memcpy(pos + 1, ctx->prepath, pplen);
pos += pplen;
}
*pos = '\0'; /* add trailing null */
convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb));
cifs_dbg(FYI, "%s: full_path=%s\n", __func__, full_path);
return full_path;
}
static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
{
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
int rc;
ctx->leaf_fullpath = (char *)full_path;
rc = cifs_mount_get_session(mnt_ctx);
ctx->leaf_fullpath = NULL;
return rc;
}
static void set_root_ses(struct cifs_mount_ctx *mnt_ctx)
{
if (mnt_ctx->ses) {
spin_lock(&cifs_tcp_ses_lock);
mnt_ctx->ses->ses_count++;
spin_unlock(&cifs_tcp_ses_lock);
dfs_cache_add_refsrv_session(&mnt_ctx->mount_id, mnt_ctx->ses);
}
mnt_ctx->root_ses = mnt_ctx->ses;
}
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)
{
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
struct dfs_info3_param ref = {};
int rc;
rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref);
if (rc)
return rc;
rc = dfs_parse_target_referral(full_path + 1, &ref, ctx);
if (rc)
goto out;
cifs_mount_put_conns(mnt_ctx);
rc = get_session(mnt_ctx, ref_path);
if (rc)
goto out;
if (ref.flags & DFSREF_REFERRAL_SERVER)
set_root_ses(mnt_ctx);
rc = -EREMOTE;
if (ref.flags & DFSREF_STORAGE_SERVER) {
rc = cifs_mount_get_tcon(mnt_ctx);
if (rc)
goto out;
/* some servers may not advertise referral capability under ref.flags */
if (!(ref.flags & DFSREF_REFERRAL_SERVER) &&
is_tcon_dfs(mnt_ctx->tcon))
set_root_ses(mnt_ctx);
rc = cifs_is_path_remote(mnt_ctx);
}
out:
free_dfs_info_param(&ref);
return rc;
}
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 TCP_Server_Info *server;
char *origin_fullpath = NULL;
int num_links = 0;
int rc;
ref_path = dfs_get_path(cifs_sb, ctx->UNC);
if (IS_ERR(ref_path))
return PTR_ERR(ref_path);
full_path = build_unc_path_to_root(ctx, cifs_sb, true);
if (IS_ERR(full_path)) {
rc = PTR_ERR(full_path);
full_path = NULL;
goto out;
}
origin_fullpath = kstrdup(full_path, GFP_KERNEL);
if (!origin_fullpath) {
rc = -ENOMEM;
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)
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 = build_unc_path_to_root(ctx, cifs_sb, true);
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) {
server = mnt_ctx->server;
mutex_lock(&server->refpath_lock);
server->origin_fullpath = origin_fullpath;
server->current_fullpath = server->leaf_fullpath;
mutex_unlock(&server->refpath_lock);
origin_fullpath = NULL;
}
out:
kfree(origin_fullpath);
kfree(ref_path);
kfree(full_path);
return rc;
}
int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
{
struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
int rc;
*isdfs = false;
rc = get_session(mnt_ctx, NULL);
if (rc)
return rc;
mnt_ctx->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.
*
* Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
* to respond with PATH_NOT_COVERED to requests that include the prefix.
*/
if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) ||
dfs_get_referral(mnt_ctx, ctx->UNC + 1, NULL, NULL)) {
rc = cifs_mount_get_tcon(mnt_ctx);
if (rc)
return rc;
rc = cifs_is_path_remote(mnt_ctx);
if (!rc || rc != -EREMOTE)
return rc;
}
*isdfs = true;
set_root_ses(mnt_ctx);
return __dfs_mount_share(mnt_ctx);
}
......@@ -8,9 +8,24 @@
#include "cifsglob.h"
#include "fs_context.h"
#include "cifs_unicode.h"
int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
struct smb3_fs_context *ctx);
int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs);
static inline char *dfs_get_path(struct cifs_sb_info *cifs_sb, const char *path)
{
return dfs_cache_canonical_path(path, cifs_sb->local_nls, cifs_remap(cifs_sb));
}
static inline int dfs_get_referral(struct cifs_mount_ctx *mnt_ctx, const char *path,
struct dfs_info3_param *ref, struct dfs_cache_tgt_list *tl)
{
struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
return dfs_cache_find(mnt_ctx->xid, mnt_ctx->root_ses, cifs_sb->local_nls,
cifs_remap(cifs_sb), path, ref, tl);
}
#endif /* _CIFS_DFS_H */
......@@ -1519,12 +1519,8 @@ static void refresh_mounts(struct cifs_ses **sessions)
spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
spin_lock(&server->srv_lock);
if (!server->is_dfs_conn) {
spin_unlock(&server->srv_lock);
if (!server->leaf_fullpath)
continue;
}
spin_unlock(&server->srv_lock);
list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
......@@ -1545,12 +1541,8 @@ static void refresh_mounts(struct cifs_ses **sessions)
list_del_init(&tcon->ulist);
mutex_lock(&server->refpath_lock);
if (server->origin_fullpath) {
if (server->leaf_fullpath && strcasecmp(server->leaf_fullpath,
server->origin_fullpath))
__refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, false);
__refresh_tcon(server->origin_fullpath + 1, sessions, tcon, false);
}
if (server->leaf_fullpath)
__refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, false);
mutex_unlock(&server->refpath_lock);
cifs_put_tcon(tcon);
......
......@@ -316,6 +316,7 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
new_ctx->UNC = NULL;
new_ctx->source = NULL;
new_ctx->iocharset = NULL;
new_ctx->leaf_fullpath = NULL;
/*
* Make sure to stay in sync with smb3_cleanup_fs_context_contents()
*/
......@@ -328,6 +329,7 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
DUP_CTX_STR(domainname);
DUP_CTX_STR(nodename);
DUP_CTX_STR(iocharset);
DUP_CTX_STR(leaf_fullpath);
return 0;
}
......@@ -1592,6 +1594,8 @@ smb3_cleanup_fs_context_contents(struct smb3_fs_context *ctx)
ctx->iocharset = NULL;
kfree(ctx->prepath);
ctx->prepath = NULL;
kfree(ctx->leaf_fullpath);
ctx->leaf_fullpath = NULL;
}
void
......
......@@ -264,6 +264,7 @@ struct smb3_fs_context {
__u16 compression; /* compression algorithm 0xFFFF default 0=disabled */
bool rootfs:1; /* if it's a SMB root file system */
bool witness:1; /* use witness protocol */
char *leaf_fullpath;
};
extern const struct fs_parameter_spec smb3_fs_parameters[];
......
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