Commit d8079fac authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag '5.14-rc2-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6

Pull cifs fixes from Steve French:
 "Five cifs/smb3 fixes, including a DFS failover fix, two fallocate
  fixes, and two trivial coverity cleanups"

* tag '5.14-rc2-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  cifs: fix fallocate when trying to allocate a hole.
  CIFS: Clarify SMB1 code for POSIX delete file
  CIFS: Clarify SMB1 code for POSIX Create
  cifs: support share failover when remounting
  cifs: only write 64kb at a time when fallocating a small region of a file
parents 6498f615 488968a8
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -873,8 +873,11 @@ CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon,
				InformationLevel) - 4;
	offset = param_offset + params;

	/* Setup pointer to Request Data (inode type) */
	pRqD = (struct unlink_psx_rq *)(((char *)&pSMB->hdr.Protocol) + offset);
	/* Setup pointer to Request Data (inode type).
	 * Note that SMB offsets are from the beginning of SMB which is 4 bytes
	 * in, after RFC1001 field
	 */
	pRqD = (struct unlink_psx_rq *)((char *)(pSMB) + offset + 4);
	pRqD->type = cpu_to_le16(type);
	pSMB->ParameterOffset = cpu_to_le16(param_offset);
	pSMB->DataOffset = cpu_to_le16(offset);
@@ -1081,7 +1084,8 @@ CIFSPOSIXCreate(const unsigned int xid, struct cifs_tcon *tcon,
	param_offset = offsetof(struct smb_com_transaction2_spi_req,
				InformationLevel) - 4;
	offset = param_offset + params;
	pdata = (OPEN_PSX_REQ *)(((char *)&pSMB->hdr.Protocol) + offset);
	/* SMB offsets are from the beginning of SMB which is 4 bytes in, after RFC1001 field */
	pdata = (OPEN_PSX_REQ *)((char *)(pSMB) + offset + 4);
	pdata->Level = cpu_to_le16(SMB_QUERY_FILE_UNIX_BASIC);
	pdata->Permissions = cpu_to_le64(mode);
	pdata->PosixOpenFlags = cpu_to_le32(posix_flags);
+2 −2
Original line number Diff line number Diff line
@@ -220,7 +220,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
#ifdef CONFIG_CIFS_DFS_UPCALL
	struct super_block *sb = NULL;
	struct cifs_sb_info *cifs_sb = NULL;
	struct dfs_cache_tgt_list tgt_list = {0};
	struct dfs_cache_tgt_list tgt_list = DFS_CACHE_TGT_LIST_INIT(tgt_list);
	struct dfs_cache_tgt_iterator *tgt_it = NULL;
#endif

@@ -3130,7 +3130,7 @@ static int do_dfs_failover(const char *path, const char *full_path, struct cifs_
{
	int rc;
	char *npath = NULL;
	struct dfs_cache_tgt_list tgt_list = {0};
	struct dfs_cache_tgt_list tgt_list = DFS_CACHE_TGT_LIST_INIT(tgt_list);
	struct dfs_cache_tgt_iterator *tgt_it = NULL;
	struct smb3_fs_context tmp_ctx = {NULL};

+191 −38
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#include "cifs_debug.h"
#include "cifs_unicode.h"
#include "smb2glob.h"
#include "dns_resolve.h"

#include "dfs_cache.h"

@@ -911,6 +912,7 @@ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl)

err_free_it:
	list_for_each_entry_safe(it, nit, head, it_list) {
		list_del(&it->it_list);
		kfree(it->it_name);
		kfree(it);
	}
@@ -1293,54 +1295,107 @@ int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it,
	return 0;
}

static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, const char *s2)
{
	char unc[sizeof("\\\\") + SERVER_NAME_LENGTH] = {0};
	const char *host;
	size_t hostlen;
	char *ip = NULL;
	struct sockaddr sa;
	bool match;
	int rc;

	if (strcasecmp(s1, s2))
		return false;

	/*
 * Refresh all active dfs mounts regardless of whether they are in cache or not.
 * (cache can be cleared)
	 * Resolve share's hostname and check if server address matches.  Otherwise just ignore it
	 * as we could not have upcall to resolve hostname or failed to convert ip address.
	 */
static void refresh_mounts(struct cifs_ses **sessions)
{
	struct TCP_Server_Info *server;
	struct cifs_ses *ses;
	struct cifs_tcon *tcon, *ntcon;
	struct list_head tcons;
	unsigned int xid;
	match = true;
	extract_unc_hostname(s1, &host, &hostlen);
	scnprintf(unc, sizeof(unc), "\\\\%.*s", (int)hostlen, host);

	rc = dns_resolve_server_name_to_ip(unc, &ip, NULL);
	if (rc < 0) {
		cifs_dbg(FYI, "%s: could not resolve %.*s. assuming server address matches.\n",
			 __func__, (int)hostlen, host);
		return true;
	}

	INIT_LIST_HEAD(&tcons);
	if (!cifs_convert_address(&sa, ip, strlen(ip))) {
		cifs_dbg(VFS, "%s: failed to convert address \'%s\'. skip address matching.\n",
			 __func__, ip);
	} else {
		mutex_lock(&server->srv_mutex);
		match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, &sa);
		mutex_unlock(&server->srv_mutex);
	}

	spin_lock(&cifs_tcp_ses_lock);
	list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
		list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
			list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
				if (tcon->dfs_path) {
					tcon->tc_count++;
					list_add_tail(&tcon->ulist, &tcons);
	kfree(ip);
	return match;
}

/*
 * Mark dfs tcon for reconnecting when the currently connected tcon does not match any of the new
 * target shares in @refs.
 */
static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cache_tgt_list *tl,
					 const struct dfs_info3_param *refs, int numrefs)
{
	struct dfs_cache_tgt_iterator *it;
	int i;

	for (it = dfs_cache_get_tgt_iterator(tl); it; it = dfs_cache_get_next_tgt(tl, it)) {
		for (i = 0; i < numrefs; i++) {
			if (target_share_equal(tcon->ses->server, dfs_cache_get_tgt_name(it),
					       refs[i].node_name))
				return;
		}
	}

	cifs_dbg(FYI, "%s: no cached or matched targets. mark dfs share for reconnect.\n", __func__);
	for (i = 0; i < tcon->ses->chan_count; i++) {
		spin_lock(&GlobalMid_Lock);
		if (tcon->ses->chans[i].server->tcpStatus != CifsExiting)
			tcon->ses->chans[i].server->tcpStatus = CifsNeedReconnect;
		spin_unlock(&GlobalMid_Lock);
	}
}
	spin_unlock(&cifs_tcp_ses_lock);

	list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
/* Refresh dfs referral of tcon and mark it for reconnect if needed */
static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh)
{
	const char *path = tcon->dfs_path + 1;
	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;

		list_del_init(&tcon->ulist);
	unsigned int xid;

	ses = find_ipc_from_server_path(sessions, path);
		if (IS_ERR(ses))
			goto next_tcon;
	if (IS_ERR(ses)) {
		cifs_dbg(FYI, "%s: could not find ipc session\n", __func__);
		return PTR_ERR(ses);
	}

	down_read(&htable_rw_lock);
	ce = lookup_cache_entry(path);
		needs_refresh = IS_ERR(ce) || cache_entry_expired(ce);
	needs_refresh = force_refresh || IS_ERR(ce) || cache_entry_expired(ce);
	if (!IS_ERR(ce)) {
		rc = get_targets(ce, &tl);
		if (rc)
			cifs_dbg(FYI, "%s: could not get dfs targets: %d\n", __func__, rc);
	}
	up_read(&htable_rw_lock);

		if (!needs_refresh)
			goto next_tcon;
	if (!needs_refresh) {
		rc = 0;
		goto out;
	}

	xid = get_xid();
	rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
@@ -1348,17 +1403,115 @@ static void refresh_mounts(struct cifs_ses **sessions)

	/* Create or update a cache entry with the new referral */
	if (!rc) {
		dump_refs(refs, numrefs);

		down_write(&htable_rw_lock);
		ce = lookup_cache_entry(path);
		if (IS_ERR(ce))
			add_cache_entry_locked(refs, numrefs);
			else if (cache_entry_expired(ce))
		else if (force_refresh || cache_entry_expired(ce))
			update_cache_entry_locked(ce, refs, numrefs);
		up_write(&htable_rw_lock);

		mark_for_reconnect_if_needed(tcon, &tl, refs, numrefs);
	}

next_tcon:
out:
	dfs_cache_free_tgts(&tl);
	free_dfs_info_array(refs, numrefs);
	return rc;
}

/**
 * dfs_cache_remount_fs - remount a DFS share
 *
 * Reconfigure dfs mount by forcing a new DFS referral and if the currently cached targets do not
 * match any of the new targets, mark it for reconnect.
 *
 * @cifs_sb: cifs superblock.
 *
 * Return zero if remounted, otherwise non-zero.
 */
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
{
	struct cifs_tcon *tcon;
	struct mount_group *mg;
	struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
	int rc;

	if (!cifs_sb || !cifs_sb->master_tlink)
		return -EINVAL;

	tcon = cifs_sb_master_tcon(cifs_sb);
	if (!tcon->dfs_path) {
		cifs_dbg(FYI, "%s: not a dfs tcon\n", __func__);
		return 0;
	}

	if (uuid_is_null(&cifs_sb->dfs_mount_id)) {
		cifs_dbg(FYI, "%s: tcon has 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: tcon has ipc session to refresh 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).
	 */
	cifs_autodisable_serverino(cifs_sb);
	/*
	 * Force the use of prefix path to support failover on DFS paths that resolve to targets
	 * 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;
}

/*
 * Refresh all active dfs mounts regardless of whether they are in cache or not.
 * (cache can be cleared)
 */
static void refresh_mounts(struct cifs_ses **sessions)
{
	struct TCP_Server_Info *server;
	struct cifs_ses *ses;
	struct cifs_tcon *tcon, *ntcon;
	struct list_head tcons;

	INIT_LIST_HEAD(&tcons);

	spin_lock(&cifs_tcp_ses_lock);
	list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
		list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
			list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
				if (tcon->dfs_path) {
					tcon->tc_count++;
					list_add_tail(&tcon->ulist, &tcons);
				}
			}
		}
	}
	spin_unlock(&cifs_tcp_ses_lock);

	list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
		list_del_init(&tcon->ulist);
		refresh_tcon(sessions, tcon, false);
		cifs_put_tcon(tcon);
	}
}
+3 −0
Original line number Diff line number Diff line
@@ -13,6 +13,8 @@
#include <linux/uuid.h>
#include "cifsglob.h"

#define DFS_CACHE_TGT_LIST_INIT(var) { .tl_numtgts = 0, .tl_list = LIST_HEAD_INIT((var).tl_list), }

struct dfs_cache_tgt_list {
	int tl_numtgts;
	struct list_head tl_list;
@@ -44,6 +46,7 @@ int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it,
void dfs_cache_put_refsrv_sessions(const uuid_t *mount_id);
void dfs_cache_add_refsrv_session(const uuid_t *mount_id, struct cifs_ses *ses);
char *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap);
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb);

static inline struct dfs_cache_tgt_iterator *
dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl,
+7 −0
Original line number Diff line number Diff line
@@ -13,6 +13,9 @@
#include <linux/magic.h>
#include <linux/security.h>
#include <net/net_namespace.h>
#ifdef CONFIG_CIFS_DFS_UPCALL
#include "dfs_cache.h"
#endif
*/

#include <linux/ctype.h>
@@ -779,6 +782,10 @@ static int smb3_reconfigure(struct fs_context *fc)
	smb3_cleanup_fs_context_contents(cifs_sb->ctx);
	rc = smb3_fs_context_dup(cifs_sb->ctx, ctx);
	smb3_update_mnt_flags(cifs_sb);
#ifdef CONFIG_CIFS_DFS_UPCALL
	if (!rc)
		rc = dfs_cache_remount_fs(cifs_sb);
#endif

	return rc;
}
Loading