Skip to content

Commit

Permalink
Merge tag '5.14-rc2-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Linus Torvalds committed Jul 25, 2021
2 parents 6498f61 + 488968a commit d8079fa
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 55 deletions.
10 changes: 7 additions & 3 deletions fs/cifs/cifssmb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions fs/cifs/connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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};

Expand Down
229 changes: 191 additions & 38 deletions fs/cifs/dfs_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "cifs_debug.h"
#include "cifs_unicode.h"
#include "smb2glob.h"
#include "dns_resolve.h"

#include "dfs_cache.h"

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -1293,6 +1295,194 @@ 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;

/*
* 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.
*/
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;
}

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

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

/* 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;
unsigned int xid;

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

down_read(&htable_rw_lock);
ce = lookup_cache_entry(path);
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) {
rc = 0;
goto out;
}

xid = get_xid();
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
free_xid(xid);

/* 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 (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);
}

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)
Expand All @@ -1303,7 +1493,6 @@ static void refresh_mounts(struct cifs_ses **sessions)
struct cifs_ses *ses;
struct cifs_tcon *tcon, *ntcon;
struct list_head tcons;
unsigned int xid;

INIT_LIST_HEAD(&tcons);

Expand All @@ -1321,44 +1510,8 @@ static void refresh_mounts(struct cifs_ses **sessions)
spin_unlock(&cifs_tcp_ses_lock);

list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
const char *path = tcon->dfs_path + 1;
struct cache_entry *ce;
struct dfs_info3_param *refs = NULL;
int numrefs = 0;
bool needs_refresh = false;
int rc = 0;

list_del_init(&tcon->ulist);

ses = find_ipc_from_server_path(sessions, path);
if (IS_ERR(ses))
goto next_tcon;

down_read(&htable_rw_lock);
ce = lookup_cache_entry(path);
needs_refresh = IS_ERR(ce) || cache_entry_expired(ce);
up_read(&htable_rw_lock);

if (!needs_refresh)
goto next_tcon;

xid = get_xid();
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
free_xid(xid);

/* Create or update a cache entry with the new referral */
if (!rc) {
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))
update_cache_entry_locked(ce, refs, numrefs);
up_write(&htable_rw_lock);
}

next_tcon:
free_dfs_info_array(refs, numrefs);
refresh_tcon(sessions, tcon, false);
cifs_put_tcon(tcon);
}
}
Expand Down
3 changes: 3 additions & 0 deletions fs/cifs/dfs_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions fs/cifs/fs_context.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit d8079fa

Please sign in to comment.