Commit d8079fac authored by Linus Torvalds's avatar Linus Torvalds

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
......@@ -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);
......
......@@ -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};
......
......@@ -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;
}
/*
* 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)
static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, const char *s2)
{
struct TCP_Server_Info *server;
struct cifs_ses *ses;
struct cifs_tcon *tcon, *ntcon;
struct list_head tcons;
unsigned int xid;
char unc[sizeof("\\\\") + SERVER_NAME_LENGTH] = {0};
const char *host;
size_t hostlen;
char *ip = NULL;
struct sockaddr sa;
bool match;
int rc;
INIT_LIST_HEAD(&tcons);
if (strcasecmp(s1, s2))
return false;
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);
/*
* 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);
}
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);
}
}
......
......@@ -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,
......
......@@ -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;
}
......
......@@ -3617,7 +3617,7 @@ static int smb3_simple_fallocate_write_range(unsigned int xid,
char *buf)
{
struct cifs_io_parms io_parms = {0};
int nbytes;
int rc, nbytes;
struct kvec iov[2];
io_parms.netfid = cfile->fid.netfid;
......@@ -3625,13 +3625,25 @@ static int smb3_simple_fallocate_write_range(unsigned int xid,
io_parms.tcon = tcon;
io_parms.persistent_fid = cfile->fid.persistent_fid;
io_parms.volatile_fid = cfile->fid.volatile_fid;
while (len) {
io_parms.offset = off;
io_parms.length = len;
if (io_parms.length > SMB2_MAX_BUFFER_SIZE)
io_parms.length = SMB2_MAX_BUFFER_SIZE;
/* iov[0] is reserved for smb header */
iov[1].iov_base = buf;
iov[1].iov_len = io_parms.length;
return SMB2_write(xid, &io_parms, &nbytes, iov, 1);
rc = SMB2_write(xid, &io_parms, &nbytes, iov, 1);
if (rc)
break;
if (nbytes > len)
return -EINVAL;
buf += nbytes;
off += nbytes;
len -= nbytes;
}
return rc;
}
static int smb3_simple_fallocate_range(unsigned int xid,
......@@ -3655,11 +3667,6 @@ static int smb3_simple_fallocate_range(unsigned int xid,
(char **)&out_data, &out_data_len);
if (rc)
goto out;
/*
* It is already all allocated
*/
if (out_data_len == 0)
goto out;
buf = kzalloc(1024 * 1024, GFP_KERNEL);
if (buf == NULL) {
......@@ -3782,6 +3789,24 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
goto out;
}
if (keep_size == true) {
/*
* We can not preallocate pages beyond the end of the file
* in SMB2
*/
if (off >= i_size_read(inode)) {
rc = 0;
goto out;
}
/*
* For fallocates that are partially beyond the end of file,
* clamp len so we only fallocate up to the end of file.
*/
if (off + len > i_size_read(inode)) {
len = i_size_read(inode) - off;
}
}
if ((keep_size == true) || (i_size_read(inode) >= off + len)) {
/*
* At this point, we are trying to fallocate an internal
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment