Commit 0a924817 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

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
parents 6022ec6e aacfc939
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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

+8 −0
Original line number Diff line number Diff line
@@ -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;
+56 −199
Original line number Diff line number Diff line
@@ -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;

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

free_full_path:
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;
+0 −6
Original line number Diff line number Diff line
@@ -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);
+2 −2
Original line number Diff line number Diff line
@@ -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 */
Loading