20 cifs/smb3 client fixes, mostly related to reconnect and/or DFS

-----BEGIN PGP SIGNATURE-----
 
 iQGzBAABCgAdFiEE6fsu8pdIjtWE/DpLiiy9cAdyT1EFAmOiJRoACgkQiiy9cAdy
 T1FsBgv/T56TVFpUism0hwWUKEeL/2HtlOk0Ic9w+2s9CtMm4j2T5x4/Bs5mq+aS
 Bs9yuH2ljYB8aKvvLQ9y2C96ojpm2/IJkSOUkIBhkyzSsQ5ckp0eO/HHHlIiwdiQ
 4LZ00qtZCvM9ON/904dETblaPNLK2vdh6p8LzpeDDF2prXuNn5PLhq/febiH+4y4
 gmj00BVb+z7okNWmKEuzhsVPFQmrNhNw56nwIA/pZsYVSHZR33OV9YlkuyruTdj7
 OIKovr522wNn+d+qn9EIQyrjjxF8uy4Sa6PZEXUhFm3wW21bBO7/J6ytRRe/FeJs
 6rgy7yHOpNrz/lxrMAFJLBWNeFuDixt4EhH+PvF0t/fcktJvgthbvzBKIiRD5utf
 cn5t5ylD5SjUM0H48px2IuOvwuLgl43DG0cfh6fj99KuyqqRtA72B0NFDcaC5xMu
 wLzlNQ33t7YspVsxXrRnkQQTR6V8rFBkrzS2QRrXEdI97lPAjWcZj+yaC1E0KHCj
 6/BzMwds
 =i8p4
 -----END PGP SIGNATURE-----

Merge tag '6.2-rc-smb3-client-fixes-part2' of git://git.samba.org/sfrench/cifs-2.6

Pull cifs fixes from Steve French:
 "cifs/smb3 client fixes, mostly related to reconnect and/or DFS:

   - two important reconnect fixes: cases where status of recently
     connected IPCs and shares were not being updated leaving them in an
     incorrect state

   - fix for older Windows servers that would return
     STATUS_OBJECT_NAME_INVALID to query info requests on DFS links in a
     namespace that contained non-ASCII characters, reducing number of
     wasted roundtrips.

   - fix for leaked -ENOMEM to userspace when cifs.ko couldn't perform
     I/O due to a disconnected server, expired or deleted session.

   - removal of all unneeded DFS related mount option string parsing
     (now using fs_context for automounts)

   - improve clarity/readability, moving various DFS related functions
     out of fs/cifs/connect.c (which was getting too big to be readable)
     to new file.

   - Fix problem when large number of DFS connections. Allow sharing of
     DFS connections and fix how the referral paths are matched

   - Referral caching fix: Instead of looking up ipc connections to
     refresh cached referrals, store direct dfs root server's IPC
     pointer in new sessions so it can simply be accessed to either
     refresh or create a new referral that such connections belong to.

   - Fix to allow dfs root server's connections to also failover

   - Optimized reconnect of nested DFS links

   - Set correct status of IPC connections marked for reconnect"

* tag '6.2-rc-smb3-client-fixes-part2' of git://git.samba.org/sfrench/cifs-2.6:
  cifs: update internal module number
  cifs: don't leak -ENOMEM in smb2_open_file()
  cifs: use origin fullpath for automounts
  cifs: set correct status of tcon ipc when reconnecting
  cifs: optimize reconnect of nested links
  cifs: fix source pathname comparison of dfs supers
  cifs: fix confusing debug message
  cifs: don't block in dfs_cache_noreq_update_tgthint()
  cifs: refresh root referrals
  cifs: fix refresh of cached referrals
  cifs: don't refresh cached referrals from unactive mounts
  cifs: share dfs connections and supers
  cifs: split out ses and tcon retrieval from mount_get_conns()
  cifs: set resolved ip in sockaddr
  cifs: remove unused smb3_fs_context::mount_options
  cifs: get rid of mount options string parsing
  cifs: use fs_context for automounts
  cifs: reduce roundtrips on create/qinfo requests
  cifs: set correct ipc status after initial tree connect
  cifs: set correct tcon status after initial tree connect
This commit is contained in:
Linus Torvalds 2022-12-21 10:40:08 -08:00
commit 0a924817d2
22 changed files with 994 additions and 1341 deletions

View file

@ -21,7 +21,7 @@ cifs-$(CONFIG_CIFS_XATTR) += xattr.o
cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o
cifs-$(CONFIG_CIFS_DFS_UPCALL) += cifs_dfs_ref.o dfs_cache.o
cifs-$(CONFIG_CIFS_DFS_UPCALL) += cifs_dfs_ref.o dfs_cache.o dfs.o
cifs-$(CONFIG_CIFS_SWN_UPCALL) += netlink.o cifs_swn.o

View file

@ -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;

View file

@ -21,8 +21,7 @@
#include "cifsfs.h"
#include "dns_resolve.h"
#include "cifs_debug.h"
#include "cifs_unicode.h"
#include "dfs_cache.h"
#include "dfs.h"
#include "fs_context.h"
static LIST_HEAD(cifs_dfs_automount_list);
@ -60,7 +59,7 @@ void cifs_dfs_release_automount_timer(void)
* Returns pointer to the built string, or a ERR_PTR. Caller is responsible
* for freeing the returned string.
*/
static char *
char *
cifs_build_devname(char *nodename, const char *prepath)
{
size_t pplen;
@ -119,200 +118,34 @@ cifs_build_devname(char *nodename, const char *prepath)
return dev;
}
/**
* cifs_compose_mount_options - creates mount options for referral
* @sb_mountdata: parent/root DFS mount options (template)
* @fullpath: full path in UNC format
* @ref: optional server's referral
* @devname: return the built cifs device name if passed pointer not NULL
* creates mount options for submount based on template options sb_mountdata
* and replacing unc,ip,prefixpath options with ones we've got form ref_unc.
*
* Returns: pointer to new mount options or ERR_PTR.
* Caller is responsible for freeing returned value if it is not error.
*/
char *cifs_compose_mount_options(const char *sb_mountdata,
const char *fullpath,
const struct dfs_info3_param *ref,
char **devname)
static int set_dest_addr(struct smb3_fs_context *ctx, const char *full_path)
{
struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;
int rc;
char *name;
char *mountdata = NULL;
const char *prepath = NULL;
int md_len;
char *tkn_e;
char *srvIP = NULL;
char sep = ',';
int off, noff;
if (sb_mountdata == NULL)
return ERR_PTR(-EINVAL);
if (ref) {
if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
return ERR_PTR(-EINVAL);
if (strlen(fullpath) - ref->path_consumed) {
prepath = fullpath + ref->path_consumed;
/* skip initial delimiter */
if (*prepath == '/' || *prepath == '\\')
prepath++;
}
name = cifs_build_devname(ref->node_name, prepath);
if (IS_ERR(name)) {
rc = PTR_ERR(name);
name = NULL;
goto compose_mount_options_err;
}
} else {
name = cifs_build_devname((char *)fullpath, NULL);
if (IS_ERR(name)) {
rc = PTR_ERR(name);
name = NULL;
goto compose_mount_options_err;
}
}
rc = dns_resolve_server_name_to_ip(name, &srvIP, NULL);
if (rc < 0) {
cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n",
__func__, name, rc);
goto compose_mount_options_err;
}
/*
* In most cases, we'll be building a shorter string than the original,
* but we do have to assume that the address in the ip= option may be
* much longer than the original. Add the max length of an address
* string to the length of the original string to allow for worst case.
*/
md_len = strlen(sb_mountdata) + INET6_ADDRSTRLEN;
mountdata = kzalloc(md_len + sizeof("ip=") + 1, GFP_KERNEL);
if (mountdata == NULL) {
rc = -ENOMEM;
goto compose_mount_options_err;
}
/* copy all options except of unc,ip,prefixpath */
off = 0;
if (strncmp(sb_mountdata, "sep=", 4) == 0) {
sep = sb_mountdata[4];
strncpy(mountdata, sb_mountdata, 5);
off += 5;
}
do {
tkn_e = strchr(sb_mountdata + off, sep);
if (tkn_e == NULL)
noff = strlen(sb_mountdata + off);
else
noff = tkn_e - (sb_mountdata + off) + 1;
if (strncasecmp(sb_mountdata + off, "cruid=", 6) == 0) {
off += noff;
continue;
}
if (strncasecmp(sb_mountdata + off, "unc=", 4) == 0) {
off += noff;
continue;
}
if (strncasecmp(sb_mountdata + off, "ip=", 3) == 0) {
off += noff;
continue;
}
if (strncasecmp(sb_mountdata + off, "prefixpath=", 11) == 0) {
off += noff;
continue;
}
strncat(mountdata, sb_mountdata + off, noff);
off += noff;
} while (tkn_e);
strcat(mountdata, sb_mountdata + off);
mountdata[md_len] = '\0';
/* copy new IP and ref share name */
if (mountdata[strlen(mountdata) - 1] != sep)
strncat(mountdata, &sep, 1);
strcat(mountdata, "ip=");
strcat(mountdata, srvIP);
if (devname)
*devname = name;
else
kfree(name);
/*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/
/*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/
compose_mount_options_out:
kfree(srvIP);
return mountdata;
compose_mount_options_err:
kfree(mountdata);
mountdata = ERR_PTR(rc);
kfree(name);
goto compose_mount_options_out;
}
/**
* cifs_dfs_do_mount - mounts specified path using DFS full path
*
* Always pass down @fullpath to smb3_do_mount() so we can use the root server
* to perform failover in case we failed to connect to the first target in the
* referral.
*
* @mntpt: directory entry for the path we are trying to automount
* @cifs_sb: parent/root superblock
* @fullpath: full path in UNC format
*/
static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt,
struct cifs_sb_info *cifs_sb,
const char *fullpath)
{
struct vfsmount *mnt;
char *mountdata;
char *devname;
devname = kstrdup(fullpath, GFP_KERNEL);
if (!devname)
return ERR_PTR(-ENOMEM);
convert_delimiter(devname, '/');
/* TODO: change to call fs_context_for_mount(), fill in context directly, call fc_mount */
/* See afs_mntpt_do_automount in fs/afs/mntpt.c for an example */
/* strip first '\' from fullpath */
mountdata = cifs_compose_mount_options(cifs_sb->ctx->mount_options,
fullpath + 1, NULL, NULL);
if (IS_ERR(mountdata)) {
kfree(devname);
return (struct vfsmount *)mountdata;
}
mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata);
kfree(mountdata);
kfree(devname);
return mnt;
rc = dns_resolve_server_name_to_ip(full_path, addr, NULL);
if (!rc)
cifs_set_port(addr, ctx->port);
return rc;
}
/*
* Create a vfsmount that we can automount
*/
static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
static struct vfsmount *cifs_dfs_do_automount(struct path *path)
{
int rc;
struct dentry *mntpt = path->dentry;
struct fs_context *fc;
struct cifs_sb_info *cifs_sb;
void *page;
void *page = NULL;
struct smb3_fs_context *ctx, *cur_ctx;
struct smb3_fs_context tmp;
char *full_path;
struct vfsmount *mnt;
cifs_dbg(FYI, "in %s\n", __func__);
BUG_ON(IS_ROOT(mntpt));
if (IS_ROOT(mntpt))
return ERR_PTR(-ESTALE);
/*
* The MSDFS spec states that paths in DFS referral requests and
@ -321,29 +154,53 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
* gives us the latter, so we must adjust the result.
*/
cifs_sb = CIFS_SB(mntpt->d_sb);
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) {
mnt = ERR_PTR(-EREMOTE);
goto cdda_exit;
}
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
return ERR_PTR(-EREMOTE);
cur_ctx = cifs_sb->ctx;
fc = fs_context_for_submount(path->mnt->mnt_sb->s_type, mntpt);
if (IS_ERR(fc))
return ERR_CAST(fc);
ctx = smb3_fc2context(fc);
page = alloc_dentry_path();
/* always use tree name prefix */
full_path = build_path_from_dentry_optional_prefix(mntpt, page, true);
full_path = dfs_get_automount_devname(mntpt, page);
if (IS_ERR(full_path)) {
mnt = ERR_CAST(full_path);
goto free_full_path;
goto out;
}
convert_delimiter(full_path, '\\');
convert_delimiter(full_path, '/');
cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path);
mnt = cifs_dfs_do_mount(mntpt, cifs_sb, full_path);
cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, full_path + 1, mnt);
tmp = *cur_ctx;
tmp.source = full_path;
tmp.leaf_fullpath = NULL;
tmp.UNC = tmp.prepath = NULL;
free_full_path:
rc = smb3_fs_context_dup(ctx, &tmp);
if (rc) {
mnt = ERR_PTR(rc);
goto out;
}
rc = set_dest_addr(ctx, full_path);
if (rc) {
mnt = ERR_PTR(rc);
goto out;
}
rc = smb3_parse_devname(full_path, ctx);
if (!rc)
mnt = fc_mount(fc);
else
mnt = ERR_PTR(rc);
out:
put_fs_context(fc);
free_dentry_path(page);
cdda_exit:
cifs_dbg(FYI, "leaving %s\n" , __func__);
return mnt;
}
@ -354,9 +211,9 @@ struct vfsmount *cifs_dfs_d_automount(struct path *path)
{
struct vfsmount *newmnt;
cifs_dbg(FYI, "in %s\n", __func__);
cifs_dbg(FYI, "%s: %pd\n", __func__, path->dentry);
newmnt = cifs_dfs_do_automount(path->dentry);
newmnt = cifs_dfs_do_automount(path);
if (IS_ERR(newmnt)) {
cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__);
return newmnt;

View file

@ -896,12 +896,6 @@ cifs_smb3_do_mount(struct file_system_type *fs_type,
goto out;
}
rc = cifs_setup_volume_info(cifs_sb->ctx, NULL, NULL);
if (rc) {
root = ERR_PTR(rc);
goto out;
}
rc = cifs_setup_cifs_sb(cifs_sb);
if (rc) {
root = ERR_PTR(rc);

View file

@ -153,6 +153,6 @@ extern const struct export_operations cifs_export_ops;
#endif /* CONFIG_CIFS_NFSD_EXPORT */
/* when changing internal version - update following two lines at same time */
#define SMB3_PRODUCT_BUILD 40
#define CIFS_VERSION "2.40"
#define SMB3_PRODUCT_BUILD 41
#define CIFS_VERSION "2.41"
#endif /* _CIFSFS_H */

View file

@ -107,6 +107,8 @@
#define CIFS_MAX_WORKSTATION_LEN (__NEW_UTS_LEN + 1) /* reasonable max for client */
#define CIFS_DFS_ROOT_SES(ses) ((ses)->dfs_root_ses ?: (ses))
/*
* CIFS vfs client Status information (based on what we know.)
*/
@ -738,8 +740,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 +753,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)
@ -1102,6 +1101,7 @@ struct cifs_ses {
*/
unsigned long chans_need_reconnect;
/* ========= end: protected by chan_lock ======== */
struct cifs_ses *dfs_root_ses;
};
static inline bool
@ -1760,6 +1760,18 @@ struct file_list {
struct cifsFileInfo *cfile;
};
struct cifs_mount_ctx {
struct cifs_sb_info *cifs_sb;
struct smb3_fs_context *fs_ctx;
unsigned int xid;
struct TCP_Server_Info *server;
struct cifs_ses *ses;
struct cifs_tcon *tcon;
struct cifs_ses *root_ses;
uuid_t mount_id;
char *origin_fullpath, *leaf_fullpath;
};
static inline void free_dfs_info_param(struct dfs_info3_param *param)
{
if (param) {

View file

@ -57,6 +57,9 @@ extern void exit_cifs_idmap(void);
extern int init_cifs_spnego(void);
extern void exit_cifs_spnego(void);
extern const char *build_path_from_dentry(struct dentry *, void *);
char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
const char *tree, int tree_len,
bool prefix);
extern char *build_path_from_dentry_optional_prefix(struct dentry *direntry,
void *page, bool prefix);
static inline void *alloc_dentry_path(void)
@ -75,9 +78,7 @@ extern char *cifs_build_path_to_root(struct smb3_fs_context *ctx,
struct cifs_tcon *tcon,
int add_treename);
extern char *build_wildcard_path_from_dentry(struct dentry *direntry);
extern char *cifs_compose_mount_options(const char *sb_mountdata,
const char *fullpath, const struct dfs_info3_param *ref,
char **devname);
char *cifs_build_devname(char *nodename, const char *prepath);
extern void delete_mid(struct mid_q_entry *mid);
extern void release_mid(struct mid_q_entry *mid);
extern void cifs_wake_up_task(struct mid_q_entry *mid);
@ -244,6 +245,10 @@ 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);
extern void cifs_umount(struct cifs_sb_info *);
@ -561,9 +566,6 @@ extern int check_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
extern int E_md4hash(const unsigned char *passwd, unsigned char *p16,
const struct nls_table *codepage);
extern int
cifs_setup_volume_info(struct smb3_fs_context *ctx, const char *mntopts, const char *devname);
extern struct TCP_Server_Info *
cifs_find_tcp_session(struct smb3_fs_context *ctx);

File diff suppressed because it is too large Load diff

544
fs/cifs/dfs.c Normal file
View file

@ -0,0 +1,544 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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"
/**
* dfs_parse_target_referral - set fs context for dfs target referral
*
* @full_path: full path in UNC format.
* @ref: dfs referral pointer.
* @ctx: smb3 fs context pointer.
*
* Return zero if dfs referral was parsed correctly, otherwise non-zero.
*/
int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
struct smb3_fs_context *ctx)
{
int rc;
const char *prepath = NULL;
char *path;
if (!full_path || !*full_path || !ref || !ctx)
return -EINVAL;
if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
return -EINVAL;
if (strlen(full_path) - ref->path_consumed) {
prepath = full_path + ref->path_consumed;
/* skip initial delimiter */
if (*prepath == '/' || *prepath == '\\')
prepath++;
}
path = cifs_build_devname(ref->node_name, prepath);
if (IS_ERR(path))
return PTR_ERR(path);
rc = smb3_parse_devname(path, ctx);
if (rc)
goto out;
rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL);
out:
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;
if (!rc) {
struct cifs_ses *ses = mnt_ctx->ses;
mutex_lock(&ses->session_mutex);
ses->dfs_root_ses = mnt_ctx->root_ses;
mutex_unlock(&ses->session_mutex);
}
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);
}
/* 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);
kfree(server->leaf_fullpath);
server->leaf_fullpath = npath;
mutex_unlock(&server->refpath_lock);
server->current_fullpath = server->leaf_fullpath;
}
return rc;
}
static int target_share_matches_server(struct TCP_Server_Info *server, const char *tcp_host,
size_t tcp_host_len, char *share, bool *target_match)
{
int rc = 0;
const char *dfs_host;
size_t dfs_host_len;
*target_match = true;
extract_unc_hostname(share, &dfs_host, &dfs_host_len);
/* Check if hostnames or addresses match */
if (dfs_host_len != tcp_host_len || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n", __func__, (int)dfs_host_len,
dfs_host, (int)tcp_host_len, tcp_host);
rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
if (rc)
cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
}
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;
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);
struct cifs_tcon *ipc = root_ses->tcon_ipc;
char *share = NULL, *prefix = NULL;
const char *tcp_host;
size_t tcp_host_len;
struct dfs_cache_tgt_iterator *tit;
bool target_match;
extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
tit = dfs_cache_get_tgt_iterator(tl);
if (!tit) {
rc = -ENOENT;
goto out;
}
/* 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);
kfree(share);
kfree(prefix);
share = prefix = NULL;
/* Check if share matches with tcp ses */
rc = dfs_cache_get_tgt_share(server->current_fullpath + 1, tit, &share, &prefix);
if (rc) {
cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
break;
}
rc = target_share_matches_server(server, tcp_host, tcp_host_len, share,
&target_match);
if (rc)
break;
if (!target_match) {
rc = -EHOSTUNREACH;
continue;
}
dfs_cache_noreq_update_tgthint(server->current_fullpath + 1, tit);
if (ipc->need_reconnect) {
scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
rc = ops->tree_connect(xid, ipc->ses, tree, ipc, cifs_sb->local_nls);
if (rc)
break;
}
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 = 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 rc;
struct TCP_Server_Info *server = tcon->ses->server;
const struct smb_version_operations *ops = server->ops;
struct super_block *sb = NULL;
struct cifs_sb_info *cifs_sb;
struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
char *tree;
struct dfs_info3_param ref = {0};
/* only send once per connect */
spin_lock(&tcon->tc_lock);
if (tcon->ses->ses_status != SES_GOOD ||
(tcon->status != TID_NEW &&
tcon->status != TID_NEED_TCON)) {
spin_unlock(&tcon->tc_lock);
return 0;
}
tcon->status = TID_IN_TCON;
spin_unlock(&tcon->tc_lock);
tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
if (!tree) {
rc = -ENOMEM;
goto out;
}
if (tcon->ipc) {
scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
goto out;
}
sb = cifs_get_tcp_super(server);
if (IS_ERR(sb)) {
rc = PTR_ERR(sb);
cifs_dbg(VFS, "%s: could not find superblock: %d\n", __func__, rc);
goto out;
}
cifs_sb = CIFS_SB(sb);
/* If it is not dfs or there was no cached dfs referral, then reconnect to same share */
if (!server->current_fullpath ||
dfs_cache_noreq_find(server->current_fullpath + 1, &ref, &tl)) {
rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon, cifs_sb->local_nls);
goto out;
}
rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
&tl);
free_dfs_info_param(&ref);
out:
kfree(tree);
cifs_put_tcp_super(sb);
if (rc) {
spin_lock(&tcon->tc_lock);
if (tcon->status == TID_IN_TCON)
tcon->status = TID_NEED_TCON;
spin_unlock(&tcon->tc_lock);
} else {
spin_lock(&tcon->tc_lock);
if (tcon->status == TID_IN_TCON)
tcon->status = TID_GOOD;
spin_unlock(&tcon->tc_lock);
tcon->need_reconnect = false;
}
return rc;
}

46
fs/cifs/dfs.h Normal file
View file

@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2022 Paulo Alcantara <palcantara@suse.de>
*/
#ifndef _CIFS_DFS_H
#define _CIFS_DFS_H
#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);
}
static inline char *dfs_get_automount_devname(struct dentry *dentry, void *page)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb);
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
struct TCP_Server_Info *server = tcon->ses->server;
if (unlikely(!server->origin_fullpath))
return ERR_PTR(-EREMOTE);
return __build_path_from_dentry_optional_prefix(dentry, page,
server->origin_fullpath,
strlen(server->origin_fullpath),
true);
}
#endif /* _CIFS_DFS_H */

View file

@ -83,27 +83,6 @@ static void refresh_cache_worker(struct work_struct *work);
static DECLARE_DELAYED_WORK(refresh_task, refresh_cache_worker);
static void get_ipc_unc(const char *ref_path, char *ipc, size_t ipclen)
{
const char *host;
size_t len;
extract_unc_hostname(ref_path, &host, &len);
scnprintf(ipc, ipclen, "\\\\%.*s\\IPC$", (int)len, host);
}
static struct cifs_ses *find_ipc_from_server_path(struct cifs_ses **ses, const char *path)
{
char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0};
get_ipc_unc(path, unc, sizeof(unc));
for (; *ses; ses++) {
if (!strcasecmp(unc, (*ses)->tcon_ipc->tree_name))
return *ses;
}
return ERR_PTR(-ENOENT);
}
static void __mount_group_release(struct mount_group *mg)
{
int i;
@ -760,8 +739,6 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
int rc;
int i;
cifs_dbg(FYI, "%s: get an DFS referral for %s\n", __func__, path);
*refs = NULL;
*numrefs = 0;
@ -770,6 +747,7 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
if (unlikely(!cache_cp))
return -EINVAL;
cifs_dbg(FYI, "%s: ipc=%s referral=%s\n", __func__, ses->tcon_ipc->tree_name, path);
rc = ses->server->ops->get_dfs_refer(xid, ses, path, refs, numrefs, cache_cp,
NO_MAP_UNI_RSVD);
if (!rc) {
@ -1104,26 +1082,23 @@ int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,
*
* Return zero if the target hint was updated successfully, otherwise non-zero.
*/
int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it)
void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it)
{
int rc;
struct cache_entry *ce;
struct cache_dfs_tgt *t;
struct cache_entry *ce;
if (!it)
return -EINVAL;
if (!path || !it)
return;
cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
down_write(&htable_rw_lock);
if (!down_write_trylock(&htable_rw_lock))
return;
ce = lookup_cache_entry(path);
if (IS_ERR(ce)) {
rc = PTR_ERR(ce);
if (IS_ERR(ce))
goto out_unlock;
}
rc = 0;
t = ce->tgthint;
if (unlikely(!strcasecmp(it->it_name, t->name)))
@ -1140,7 +1115,6 @@ int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_
out_unlock:
up_write(&htable_rw_lock);
return rc;
}
/**
@ -1314,8 +1288,7 @@ static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, c
char unc[sizeof("\\\\") + SERVER_NAME_LENGTH] = {0};
const char *host;
size_t hostlen;
char *ip = NULL;
struct sockaddr sa;
struct sockaddr_storage ss;
bool match;
int rc;
@ -1330,23 +1303,17 @@ static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, c
extract_unc_hostname(s1, &host, &hostlen);
scnprintf(unc, sizeof(unc), "\\\\%.*s", (int)hostlen, host);
rc = dns_resolve_server_name_to_ip(unc, &ip, NULL);
rc = dns_resolve_server_name_to_ip(unc, (struct sockaddr *)&ss, NULL);
if (rc < 0) {
cifs_dbg(FYI, "%s: could not resolve %.*s. assuming server address matches.\n",
__func__, (int)hostlen, host);
return true;
}
if (!cifs_convert_address(&sa, ip, strlen(ip))) {
cifs_dbg(VFS, "%s: failed to convert address \'%s\'. skip address matching.\n",
__func__, ip);
} else {
cifs_server_lock(server);
match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, &sa);
cifs_server_unlock(server);
}
cifs_server_lock(server);
match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, (struct sockaddr *)&ss);
cifs_server_unlock(server);
kfree(ip);
return match;
}
@ -1373,23 +1340,19 @@ static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cach
}
/* Refresh dfs referral of tcon and mark it for reconnect if needed */
static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct cifs_tcon *tcon,
bool force_refresh)
static int __refresh_tcon(const char *path, struct cifs_tcon *tcon, bool force_refresh)
{
struct cifs_ses *ses;
struct cache_entry *ce;
struct dfs_info3_param *refs = NULL;
int numrefs = 0;
bool needs_refresh = false;
struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
int rc = 0;
struct cifs_ses *ses = CIFS_DFS_ROOT_SES(tcon->ses);
struct cifs_tcon *ipc = ses->tcon_ipc;
struct dfs_info3_param *refs = NULL;
bool needs_refresh = false;
struct cache_entry *ce;
unsigned int xid;
int numrefs = 0;
int rc = 0;
ses = find_ipc_from_server_path(sessions, path);
if (IS_ERR(ses)) {
cifs_dbg(FYI, "%s: could not find ipc session\n", __func__);
return PTR_ERR(ses);
}
xid = get_xid();
down_read(&htable_rw_lock);
ce = lookup_cache_entry(path);
@ -1406,12 +1369,17 @@ static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct c
goto out;
}
xid = get_xid();
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
free_xid(xid);
spin_lock(&ipc->tc_lock);
if (ses->ses_status != SES_GOOD || ipc->status != TID_GOOD) {
spin_unlock(&ipc->tc_lock);
cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n", __func__);
goto out;
}
spin_unlock(&ipc->tc_lock);
/* Create or update a cache entry with the new referral */
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
if (!rc) {
/* Create or update a cache entry with the new referral */
dump_refs(refs, numrefs);
down_write(&htable_rw_lock);
@ -1426,24 +1394,20 @@ static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct c
}
out:
free_xid(xid);
dfs_cache_free_tgts(&tl);
free_dfs_info_array(refs, numrefs);
return rc;
}
static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh)
static int refresh_tcon(struct cifs_tcon *tcon, bool force_refresh)
{
struct TCP_Server_Info *server = tcon->ses->server;
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, force_refresh);
__refresh_tcon(server->origin_fullpath + 1, sessions, tcon, force_refresh);
}
if (server->leaf_fullpath)
__refresh_tcon(server->leaf_fullpath + 1, tcon, force_refresh);
mutex_unlock(&server->refpath_lock);
return 0;
}
@ -1461,9 +1425,6 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
{
struct cifs_tcon *tcon;
struct TCP_Server_Info *server;
struct mount_group *mg;
struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
int rc;
if (!cifs_sb || !cifs_sb->master_tlink)
return -EINVAL;
@ -1480,21 +1441,6 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
cifs_dbg(FYI, "%s: no dfs mount group id\n", __func__);
return -EINVAL;
}
mutex_lock(&mount_group_list_lock);
mg = find_mount_group_locked(&cifs_sb->dfs_mount_id);
if (IS_ERR(mg)) {
mutex_unlock(&mount_group_list_lock);
cifs_dbg(FYI, "%s: no ipc session for refreshing referral\n", __func__);
return PTR_ERR(mg);
}
kref_get(&mg->refcount);
mutex_unlock(&mount_group_list_lock);
spin_lock(&mg->lock);
memcpy(&sessions, mg->sessions, mg->num_sessions * sizeof(mg->sessions[0]));
spin_unlock(&mg->lock);
/*
* After reconnecting to a different server, unique ids won't match anymore, so we disable
* serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE).
@ -1505,42 +1451,38 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
* that have different prefix paths.
*/
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
rc = refresh_tcon(sessions, tcon, true);
kref_put(&mg->refcount, mount_group_release);
return rc;
return refresh_tcon(tcon, true);
}
/*
* Refresh all active dfs mounts regardless of whether they are in cache or not.
* (cache can be cleared)
* Worker that will refresh DFS cache from all active mounts based on lowest TTL value
* from a DFS referral.
*/
static void refresh_mounts(struct cifs_ses **sessions)
static void refresh_cache_worker(struct work_struct *work)
{
struct TCP_Server_Info *server;
struct cifs_ses *ses;
struct cifs_tcon *tcon, *ntcon;
struct list_head tcons;
struct cifs_ses *ses;
INIT_LIST_HEAD(&tcons);
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) {
if (ses->tcon_ipc) {
ses->ses_count++;
list_add_tail(&ses->tcon_ipc->ulist, &tcons);
}
list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
spin_lock(&tcon->tc_lock);
if (!tcon->ipc && !tcon->need_reconnect) {
if (!tcon->ipc) {
tcon->tc_count++;
list_add_tail(&tcon->ulist, &tcons);
}
spin_unlock(&tcon->tc_lock);
}
}
}
@ -1552,132 +1494,14 @@ 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, tcon, false);
mutex_unlock(&server->refpath_lock);
cifs_put_tcon(tcon);
}
}
static void refresh_cache(struct cifs_ses **sessions)
{
int i;
struct cifs_ses *ses;
unsigned int xid;
char *ref_paths[CACHE_MAX_ENTRIES];
int count = 0;
struct cache_entry *ce;
/*
* Refresh all cached entries. Get all new referrals outside critical section to avoid
* starvation while performing SMB2 IOCTL on broken or slow connections.
* The cache entries may cover more paths than the active mounts
* (e.g. domain-based DFS referrals or multi tier DFS setups).
*/
down_read(&htable_rw_lock);
for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
struct hlist_head *l = &cache_htable[i];
hlist_for_each_entry(ce, l, hlist) {
if (count == ARRAY_SIZE(ref_paths))
goto out_unlock;
if (hlist_unhashed(&ce->hlist) || !cache_entry_expired(ce) ||
IS_ERR(find_ipc_from_server_path(sessions, ce->path)))
continue;
ref_paths[count++] = kstrdup(ce->path, GFP_ATOMIC);
}
}
out_unlock:
up_read(&htable_rw_lock);
for (i = 0; i < count; i++) {
char *path = ref_paths[i];
struct dfs_info3_param *refs = NULL;
int numrefs = 0;
int rc = 0;
if (!path)
continue;
ses = find_ipc_from_server_path(sessions, path);
if (IS_ERR(ses))
goto next_referral;
xid = get_xid();
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
free_xid(xid);
if (!rc) {
down_write(&htable_rw_lock);
ce = lookup_cache_entry(path);
/*
* We need to re-check it because other tasks might have it deleted or
* updated.
*/
if (!IS_ERR(ce) && cache_entry_expired(ce))
update_cache_entry_locked(ce, refs, numrefs);
up_write(&htable_rw_lock);
}
next_referral:
kfree(path);
free_dfs_info_array(refs, numrefs);
}
}
/*
* Worker that will refresh DFS cache and active mounts based on lowest TTL value from a DFS
* referral.
*/
static void refresh_cache_worker(struct work_struct *work)
{
struct list_head mglist;
struct mount_group *mg, *tmp_mg;
struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
int max_sessions = ARRAY_SIZE(sessions) - 1;
int i = 0, count;
INIT_LIST_HEAD(&mglist);
/* Get refereces of mount groups */
mutex_lock(&mount_group_list_lock);
list_for_each_entry(mg, &mount_group_list, list) {
kref_get(&mg->refcount);
list_add(&mg->refresh_list, &mglist);
}
mutex_unlock(&mount_group_list_lock);
/* Fill in local array with an NULL-terminated list of all referral server sessions */
list_for_each_entry(mg, &mglist, refresh_list) {
if (i >= max_sessions)
break;
spin_lock(&mg->lock);
if (i + mg->num_sessions > max_sessions)
count = max_sessions - i;
if (tcon->ipc)
cifs_put_smb_ses(tcon->ses);
else
count = mg->num_sessions;
memcpy(&sessions[i], mg->sessions, count * sizeof(mg->sessions[0]));
spin_unlock(&mg->lock);
i += count;
}
if (sessions[0]) {
/* Refresh all active mounts and cached entries */
refresh_mounts(sessions);
refresh_cache(sessions);
}
list_for_each_entry_safe(mg, tmp_mg, &mglist, refresh_list) {
list_del_init(&mg->refresh_list);
kref_put(&mg->refcount, mount_group_release);
cifs_put_tcon(tcon);
}
spin_lock(&cache_ttl_lock);

View file

@ -38,7 +38,7 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,
const struct nls_table *cp, int remap, const char *path,
const struct dfs_cache_tgt_iterator *it);
int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it);
void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it);
int dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iterator *it,
struct dfs_info3_param *ref);
int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it, char **share,

View file

@ -78,14 +78,13 @@ build_path_from_dentry(struct dentry *direntry, void *page)
prefix);
}
char *
build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
bool prefix)
char *__build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
const char *tree, int tree_len,
bool prefix)
{
int dfsplen;
int pplen = 0;
struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb);
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
char dirsep = CIFS_DIR_SEP(cifs_sb);
char *s;
@ -93,7 +92,7 @@ build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
return ERR_PTR(-ENOMEM);
if (prefix)
dfsplen = strnlen(tcon->tree_name, MAX_TREE_SIZE + 1);
dfsplen = strnlen(tree, tree_len + 1);
else
dfsplen = 0;
@ -123,7 +122,7 @@ build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
}
if (dfsplen) {
s -= dfsplen;
memcpy(s, tcon->tree_name, dfsplen);
memcpy(s, tree, dfsplen);
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) {
int i;
for (i = 0; i < dfsplen; i++) {
@ -135,6 +134,16 @@ build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
return s;
}
char *build_path_from_dentry_optional_prefix(struct dentry *direntry, void *page,
bool prefix)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb);
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
return __build_path_from_dentry_optional_prefix(direntry, page, tcon->tree_name,
MAX_TREE_SIZE, prefix);
}
/*
* Don't allow path components longer than the server max.
* Don't allow the separator character in a path component.

View file

@ -12,6 +12,7 @@
*
*/
#include <linux/inet.h>
#include <linux/slab.h>
#include <linux/dns_resolver.h>
#include "dns_resolve.h"
@ -25,17 +26,13 @@
* @ip_addr: Where to return the IP address.
* @expiry: Where to return the expiry time for the dns record.
*
* The IP address will be returned in string form, and the caller is
* responsible for freeing it.
*
* Returns length of result on success, -ve on error.
* Returns zero success, -ve on error.
*/
int
dns_resolve_server_name_to_ip(const char *unc, char **ip_addr, time64_t *expiry)
dns_resolve_server_name_to_ip(const char *unc, struct sockaddr *ip_addr, time64_t *expiry)
{
struct sockaddr_storage ss;
const char *hostname, *sep;
char *name;
char *ip;
int len, rc;
if (!ip_addr || !unc)
@ -60,30 +57,32 @@ dns_resolve_server_name_to_ip(const char *unc, char **ip_addr, time64_t *expiry)
__func__, unc);
/* Try to interpret hostname as an IPv4 or IPv6 address */
rc = cifs_convert_address((struct sockaddr *)&ss, hostname, len);
if (rc > 0)
goto name_is_IP_address;
rc = cifs_convert_address(ip_addr, hostname, len);
if (rc > 0) {
cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %*.*s\n", __func__, len, len,
hostname);
return 0;
}
/* Perform the upcall */
rc = dns_query(current->nsproxy->net_ns, NULL, hostname, len,
NULL, ip_addr, expiry, false);
if (rc < 0)
NULL, &ip, expiry, false);
if (rc < 0) {
cifs_dbg(FYI, "%s: unable to resolve: %*.*s\n",
__func__, len, len, hostname);
else
} else {
cifs_dbg(FYI, "%s: resolved: %*.*s to %s expiry %llu\n",
__func__, len, len, hostname, *ip_addr,
__func__, len, len, hostname, ip,
expiry ? (*expiry) : 0);
return rc;
name_is_IP_address:
name = kmalloc(len + 1, GFP_KERNEL);
if (!name)
return -ENOMEM;
memcpy(name, hostname, len);
name[len] = 0;
cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %s\n",
__func__, name);
*ip_addr = name;
return 0;
rc = cifs_convert_address(ip_addr, ip, strlen(ip));
kfree(ip);
if (!rc) {
cifs_dbg(FYI, "%s: unable to determine ip address\n", __func__);
rc = -EHOSTUNREACH;
} else
rc = 0;
}
return rc;
}

View file

@ -11,8 +11,10 @@
#ifndef _DNS_RESOLVE_H
#define _DNS_RESOLVE_H
#include <linux/net.h>
#ifdef __KERNEL__
extern int dns_resolve_server_name_to_ip(const char *unc, char **ip_addr, time64_t *expiry);
int dns_resolve_server_name_to_ip(const char *unc, struct sockaddr *ip_addr, time64_t *expiry);
#endif /* KERNEL */
#endif /* _DNS_RESOLVE_H */

View file

@ -308,7 +308,6 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
{
memcpy(new_ctx, ctx, sizeof(*ctx));
new_ctx->prepath = NULL;
new_ctx->mount_options = NULL;
new_ctx->nodename = NULL;
new_ctx->username = NULL;
new_ctx->password = NULL;
@ -317,11 +316,11 @@ 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()
*/
DUP_CTX_STR(prepath);
DUP_CTX_STR(mount_options);
DUP_CTX_STR(username);
DUP_CTX_STR(password);
DUP_CTX_STR(server_hostname);
@ -330,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;
}
@ -569,17 +569,12 @@ static const struct fs_context_operations smb3_fs_context_ops = {
static int smb3_fs_context_parse_monolithic(struct fs_context *fc,
void *data)
{
struct smb3_fs_context *ctx = smb3_fc2context(fc);
char *options = data, *key;
int ret = 0;
if (!options)
return 0;
ctx->mount_options = kstrdup(data, GFP_KERNEL);
if (ctx->mount_options == NULL)
return -ENOMEM;
ret = security_sb_eat_lsm_opts(options, &fc->security);
if (ret)
return ret;
@ -1581,8 +1576,6 @@ smb3_cleanup_fs_context_contents(struct smb3_fs_context *ctx)
/*
* Make sure this stays in sync with smb3_fs_context_dup()
*/
kfree(ctx->mount_options);
ctx->mount_options = NULL;
kfree(ctx->username);
ctx->username = NULL;
kfree_sensitive(ctx->password);
@ -1601,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

View file

@ -264,8 +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 *mount_options;
char *leaf_fullpath;
};
extern const struct fs_parameter_spec smb3_fs_parameters[];

View file

@ -993,12 +993,6 @@ int cifs_get_inode_info(struct inode **inode, const char *full_path,
}
rc = server->ops->query_path_info(xid, tcon, cifs_sb, full_path, &tmp_data,
&adjust_tz, &is_reparse_point);
#ifdef CONFIG_CIFS_DFS_UPCALL
if (rc == -ENOENT && is_tcon_dfs(tcon))
rc = cifs_dfs_query_info_nonascii_quirk(xid, tcon,
cifs_sb,
full_path);
#endif
data = &tmp_data;
}

View file

@ -1258,44 +1258,28 @@ int match_target_ip(struct TCP_Server_Info *server,
bool *result)
{
int rc;
char *target, *tip = NULL;
struct sockaddr tipaddr;
char *target;
struct sockaddr_storage ss;
*result = false;
target = kzalloc(share_len + 3, GFP_KERNEL);
if (!target) {
rc = -ENOMEM;
goto out;
}
if (!target)
return -ENOMEM;
scnprintf(target, share_len + 3, "\\\\%.*s", (int)share_len, share);
cifs_dbg(FYI, "%s: target name: %s\n", __func__, target + 2);
rc = dns_resolve_server_name_to_ip(target, &tip, NULL);
if (rc < 0)
goto out;
cifs_dbg(FYI, "%s: target ip: %s\n", __func__, tip);
if (!cifs_convert_address(&tipaddr, tip, strlen(tip))) {
cifs_dbg(VFS, "%s: failed to convert target ip address\n",
__func__);
rc = -EINVAL;
goto out;
}
*result = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr,
&tipaddr);
cifs_dbg(FYI, "%s: ip addresses match: %u\n", __func__, *result);
rc = 0;
out:
rc = dns_resolve_server_name_to_ip(target, (struct sockaddr *)&ss, NULL);
kfree(target);
kfree(tip);
return rc;
if (rc < 0)
return rc;
*result = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, (struct sockaddr *)&ss);
cifs_dbg(FYI, "%s: ip addresses match: %u\n", __func__, *result);
return 0;
}
int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix)
@ -1314,49 +1298,4 @@ int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix)
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
return 0;
}
/** cifs_dfs_query_info_nonascii_quirk
* Handle weird Windows SMB server behaviour. It responds with
* STATUS_OBJECT_NAME_INVALID code to SMB2 QUERY_INFO request
* for "\<server>\<dfsname>\<linkpath>" DFS reference,
* where <dfsname> contains non-ASCII unicode symbols.
*
* Check such DFS reference.
*/
int cifs_dfs_query_info_nonascii_quirk(const unsigned int xid,
struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb,
const char *linkpath)
{
char *treename, *dfspath, sep;
int treenamelen, linkpathlen, rc;
treename = tcon->tree_name;
/* MS-DFSC: All paths in REQ_GET_DFS_REFERRAL and RESP_GET_DFS_REFERRAL
* messages MUST be encoded with exactly one leading backslash, not two
* leading backslashes.
*/
sep = CIFS_DIR_SEP(cifs_sb);
if (treename[0] == sep && treename[1] == sep)
treename++;
linkpathlen = strlen(linkpath);
treenamelen = strnlen(treename, MAX_TREE_SIZE + 1);
dfspath = kzalloc(treenamelen + linkpathlen + 1, GFP_KERNEL);
if (!dfspath)
return -ENOMEM;
if (treenamelen)
memcpy(dfspath, treename, treenamelen);
memcpy(dfspath + treenamelen, linkpath, linkpathlen);
rc = dfs_cache_find(xid, tcon->ses, cifs_sb->local_nls,
cifs_remap(cifs_sb), dfspath, NULL, NULL);
if (rc == 0) {
cifs_dbg(FYI, "DFS ref '%s' is found, emulate -EREMOTE\n",
dfspath);
rc = -EREMOTE;
} else {
cifs_dbg(FYI, "%s: dfs_cache_find returned %d\n", __func__, rc);
}
kfree(dfspath);
return rc;
}
#endif

View file

@ -122,8 +122,8 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32
struct smb2_hdr *hdr = err_iov.iov_base;
if (unlikely(!err_iov.iov_base || err_buftype == CIFS_NO_BUFFER))
rc = -ENOMEM;
else if (hdr->Status == STATUS_STOPPED_ON_SYMLINK) {
goto out;
if (hdr->Status == STATUS_STOPPED_ON_SYMLINK) {
rc = smb2_parse_symlink_response(oparms->cifs_sb, &err_iov,
&data->symlink_target);
if (!rc) {

View file

@ -556,22 +556,42 @@ int smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, FILE_READ_ATTRIBUTES, FILE_OPEN,
create_options, ACL_NO_MODE, data, SMB2_OP_QUERY_INFO, cfile,
NULL, NULL, err_iov, err_buftype);
if (rc == -EOPNOTSUPP) {
if (err_iov[0].iov_base && err_buftype[0] != CIFS_NO_BUFFER &&
((struct smb2_hdr *)err_iov[0].iov_base)->Command == SMB2_CREATE &&
((struct smb2_hdr *)err_iov[0].iov_base)->Status == STATUS_STOPPED_ON_SYMLINK) {
rc = smb2_parse_symlink_response(cifs_sb, err_iov, &data->symlink_target);
if (rc) {
struct smb2_hdr *hdr = err_iov[0].iov_base;
if (unlikely(!hdr || err_buftype[0] == CIFS_NO_BUFFER))
goto out;
if (rc == -EOPNOTSUPP && hdr->Command == SMB2_CREATE &&
hdr->Status == STATUS_STOPPED_ON_SYMLINK) {
rc = smb2_parse_symlink_response(cifs_sb, err_iov,
&data->symlink_target);
if (rc)
goto out;
}
*reparse = true;
create_options |= OPEN_REPARSE_POINT;
/* Failed on a symbolic link - query a reparse point info */
cifs_get_readable_path(tcon, full_path, &cfile);
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, FILE_READ_ATTRIBUTES,
FILE_OPEN, create_options, ACL_NO_MODE, data,
SMB2_OP_QUERY_INFO, cfile, NULL, NULL, NULL, NULL);
*reparse = true;
create_options |= OPEN_REPARSE_POINT;
/* Failed on a symbolic link - query a reparse point info */
cifs_get_readable_path(tcon, full_path, &cfile);
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
FILE_READ_ATTRIBUTES, FILE_OPEN,
create_options, ACL_NO_MODE, data,
SMB2_OP_QUERY_INFO, cfile, NULL, NULL,
NULL, NULL);
goto out;
} else if (rc != -EREMOTE && IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) &&
hdr->Status == STATUS_OBJECT_NAME_INVALID) {
/*
* Handle weird Windows SMB server behaviour. It responds with
* STATUS_OBJECT_NAME_INVALID code to SMB2 QUERY_INFO request
* for "\<server>\<dfsname>\<linkpath>" DFS reference,
* where <dfsname> contains non-ASCII unicode symbols.
*/
rc = -EREMOTE;
}
if (rc == -EREMOTE && IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) && cifs_sb &&
(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS))
rc = -EOPNOTSUPP;
}
out:

View file

@ -796,7 +796,9 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
int rc;
__le16 *utf16_path;
__u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
int err_buftype = CIFS_NO_BUFFER;
struct cifs_open_parms oparms;
struct kvec err_iov = {};
struct cifs_fid fid;
struct cached_fid *cfid;
@ -820,14 +822,32 @@ smb2_is_path_accessible(const unsigned int xid, struct cifs_tcon *tcon,
oparms.fid = &fid;
oparms.reconnect = false;
rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL, NULL,
NULL);
rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, NULL,
&err_iov, &err_buftype);
if (rc) {
kfree(utf16_path);
return rc;
struct smb2_hdr *hdr = err_iov.iov_base;
if (unlikely(!hdr || err_buftype == CIFS_NO_BUFFER))
goto out;
/*
* Handle weird Windows SMB server behaviour. It responds with
* STATUS_OBJECT_NAME_INVALID code to SMB2 QUERY_INFO request
* for "\<server>\<dfsname>\<linkpath>" DFS reference,
* where <dfsname> contains non-ASCII unicode symbols.
*/
if (rc != -EREMOTE && IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) &&
hdr->Status == STATUS_OBJECT_NAME_INVALID)
rc = -EREMOTE;
if (rc == -EREMOTE && IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) && cifs_sb &&
(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS))
rc = -EOPNOTSUPP;
goto out;
}
rc = SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
out:
free_rsp_buf(err_buftype, err_iov.iov_base);
kfree(utf16_path);
return rc;
}