Skip to content

Commit

Permalink
Merge tag '6.7-rc4-smb3-client-fixes' of git://git.samba.org/sfrench/…
Browse files Browse the repository at this point in the history
…cifs-2.6

Pull smb client fixes from Steve French:
 "Six smb3 client fixes:

   - Fixes for copy_file_range and clone (cache invalidation and file
     size), also addresses an xfstest failure

   - Fix to return proper error if REMAP_FILE_DEDUP set (also fixes
     xfstest generic/304)

   - Fix potential null pointer reference with DFS

   - Multichannel fix addressing (reverting an earlier patch) some of
     the problems with enabling/disabling channels dynamically

  Still working on a followon multichannel fix to address another issue
  found in reconnect testing that will send next week"

* tag '6.7-rc4-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  cifs: reconnect worker should take reference on server struct unconditionally
  Revert "cifs: reconnect work should have reference on server struct"
  cifs: Fix non-availability of dedup breaking generic/304
  smb: client: fix potential NULL deref in parse_dfs_referrals()
  cifs: Fix flushing, invalidation and file size with FICLONE
  cifs: Fix flushing, invalidation and file size with copy_file_range()
  • Loading branch information
torvalds committed Dec 9, 2023
2 parents f2e8a57 + 0490919 commit 2099306
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 58 deletions.
174 changes: 159 additions & 15 deletions fs/smb/client/cifsfs.c
Expand Up @@ -1196,32 +1196,103 @@ const struct inode_operations cifs_symlink_inode_ops = {
.listxattr = cifs_listxattr,
};

/*
* Advance the EOF marker to after the source range.
*/
static int cifs_precopy_set_eof(struct inode *src_inode, struct cifsInodeInfo *src_cifsi,
struct cifs_tcon *src_tcon,
unsigned int xid, loff_t src_end)
{
struct cifsFileInfo *writeable_srcfile;
int rc = -EINVAL;

writeable_srcfile = find_writable_file(src_cifsi, FIND_WR_FSUID_ONLY);
if (writeable_srcfile) {
if (src_tcon->ses->server->ops->set_file_size)
rc = src_tcon->ses->server->ops->set_file_size(
xid, src_tcon, writeable_srcfile,
src_inode->i_size, true /* no need to set sparse */);
else
rc = -ENOSYS;
cifsFileInfo_put(writeable_srcfile);
cifs_dbg(FYI, "SetFSize for copychunk rc = %d\n", rc);
}

if (rc < 0)
goto set_failed;

netfs_resize_file(&src_cifsi->netfs, src_end);
fscache_resize_cookie(cifs_inode_cookie(src_inode), src_end);
return 0;

set_failed:
return filemap_write_and_wait(src_inode->i_mapping);
}

/*
* Flush out either the folio that overlaps the beginning of a range in which
* pos resides or the folio that overlaps the end of a range unless that folio
* is entirely within the range we're going to invalidate. We extend the flush
* bounds to encompass the folio.
*/
static int cifs_flush_folio(struct inode *inode, loff_t pos, loff_t *_fstart, loff_t *_fend,
bool first)
{
struct folio *folio;
unsigned long long fpos, fend;
pgoff_t index = pos / PAGE_SIZE;
size_t size;
int rc = 0;

folio = filemap_get_folio(inode->i_mapping, index);
if (IS_ERR(folio))
return 0;

size = folio_size(folio);
fpos = folio_pos(folio);
fend = fpos + size - 1;
*_fstart = min_t(unsigned long long, *_fstart, fpos);
*_fend = max_t(unsigned long long, *_fend, fend);
if ((first && pos == fpos) || (!first && pos == fend))
goto out;

rc = filemap_write_and_wait_range(inode->i_mapping, fpos, fend);
out:
folio_put(folio);
return rc;
}

static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
struct file *dst_file, loff_t destoff, loff_t len,
unsigned int remap_flags)
{
struct inode *src_inode = file_inode(src_file);
struct inode *target_inode = file_inode(dst_file);
struct cifsInodeInfo *src_cifsi = CIFS_I(src_inode);
struct cifsInodeInfo *target_cifsi = CIFS_I(target_inode);
struct cifsFileInfo *smb_file_src = src_file->private_data;
struct cifsFileInfo *smb_file_target;
struct cifs_tcon *target_tcon;
struct cifsFileInfo *smb_file_target = dst_file->private_data;
struct cifs_tcon *target_tcon, *src_tcon;
unsigned long long destend, fstart, fend, new_size;
unsigned int xid;
int rc;

if (remap_flags & ~(REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY))
if (remap_flags & REMAP_FILE_DEDUP)
return -EOPNOTSUPP;
if (remap_flags & ~REMAP_FILE_ADVISORY)
return -EINVAL;

cifs_dbg(FYI, "clone range\n");

xid = get_xid();

if (!src_file->private_data || !dst_file->private_data) {
if (!smb_file_src || !smb_file_target) {
rc = -EBADF;
cifs_dbg(VFS, "missing cifsFileInfo on copy range src file\n");
goto out;
}

smb_file_target = dst_file->private_data;
src_tcon = tlink_tcon(smb_file_src->tlink);
target_tcon = tlink_tcon(smb_file_target->tlink);

/*
Expand All @@ -1234,20 +1305,63 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
if (len == 0)
len = src_inode->i_size - off;

cifs_dbg(FYI, "about to flush pages\n");
/* should we flush first and last page first */
truncate_inode_pages_range(&target_inode->i_data, destoff,
PAGE_ALIGN(destoff + len)-1);
cifs_dbg(FYI, "clone range\n");

if (target_tcon->ses->server->ops->duplicate_extents)
/* Flush the source buffer */
rc = filemap_write_and_wait_range(src_inode->i_mapping, off,
off + len - 1);
if (rc)
goto unlock;

/* The server-side copy will fail if the source crosses the EOF marker.
* Advance the EOF marker after the flush above to the end of the range
* if it's short of that.
*/
if (src_cifsi->netfs.remote_i_size < off + len) {
rc = cifs_precopy_set_eof(src_inode, src_cifsi, src_tcon, xid, off + len);
if (rc < 0)
goto unlock;
}

new_size = destoff + len;
destend = destoff + len - 1;

/* Flush the folios at either end of the destination range to prevent
* accidental loss of dirty data outside of the range.
*/
fstart = destoff;
fend = destend;

rc = cifs_flush_folio(target_inode, destoff, &fstart, &fend, true);
if (rc)
goto unlock;
rc = cifs_flush_folio(target_inode, destend, &fstart, &fend, false);
if (rc)
goto unlock;

/* Discard all the folios that overlap the destination region. */
cifs_dbg(FYI, "about to discard pages %llx-%llx\n", fstart, fend);
truncate_inode_pages_range(&target_inode->i_data, fstart, fend);

fscache_invalidate(cifs_inode_cookie(target_inode), NULL,
i_size_read(target_inode), 0);

rc = -EOPNOTSUPP;
if (target_tcon->ses->server->ops->duplicate_extents) {
rc = target_tcon->ses->server->ops->duplicate_extents(xid,
smb_file_src, smb_file_target, off, len, destoff);
else
rc = -EOPNOTSUPP;
if (rc == 0 && new_size > i_size_read(target_inode)) {
truncate_setsize(target_inode, new_size);
netfs_resize_file(&target_cifsi->netfs, new_size);
fscache_resize_cookie(cifs_inode_cookie(target_inode),
new_size);
}
}

/* force revalidate of size and timestamps of target file now
that target is updated on the server */
CIFS_I(target_inode)->time = 0;
unlock:
/* although unlocking in the reverse order from locking is not
strictly necessary here it is a little cleaner to be consistent */
unlock_two_nondirectories(src_inode, target_inode);
Expand All @@ -1263,10 +1377,12 @@ ssize_t cifs_file_copychunk_range(unsigned int xid,
{
struct inode *src_inode = file_inode(src_file);
struct inode *target_inode = file_inode(dst_file);
struct cifsInodeInfo *src_cifsi = CIFS_I(src_inode);
struct cifsFileInfo *smb_file_src;
struct cifsFileInfo *smb_file_target;
struct cifs_tcon *src_tcon;
struct cifs_tcon *target_tcon;
unsigned long long destend, fstart, fend;
ssize_t rc;

cifs_dbg(FYI, "copychunk range\n");
Expand Down Expand Up @@ -1306,13 +1422,41 @@ ssize_t cifs_file_copychunk_range(unsigned int xid,
if (rc)
goto unlock;

/* should we flush first and last page first */
truncate_inode_pages(&target_inode->i_data, 0);
/* The server-side copy will fail if the source crosses the EOF marker.
* Advance the EOF marker after the flush above to the end of the range
* if it's short of that.
*/
if (src_cifsi->server_eof < off + len) {
rc = cifs_precopy_set_eof(src_inode, src_cifsi, src_tcon, xid, off + len);
if (rc < 0)
goto unlock;
}

destend = destoff + len - 1;

/* Flush the folios at either end of the destination range to prevent
* accidental loss of dirty data outside of the range.
*/
fstart = destoff;
fend = destend;

rc = cifs_flush_folio(target_inode, destoff, &fstart, &fend, true);
if (rc)
goto unlock;
rc = cifs_flush_folio(target_inode, destend, &fstart, &fend, false);
if (rc)
goto unlock;

/* Discard all the folios that overlap the destination region. */
truncate_inode_pages_range(&target_inode->i_data, fstart, fend);

rc = file_modified(dst_file);
if (!rc)
if (!rc) {
rc = target_tcon->ses->server->ops->copychunk_range(xid,
smb_file_src, smb_file_target, off, len, destoff);
if (rc > 0 && destoff + rc > i_size_read(target_inode))
truncate_setsize(target_inode, destoff + rc);
}

file_accessed(src_file);

Expand Down
35 changes: 10 additions & 25 deletions fs/smb/client/connect.c
Expand Up @@ -402,13 +402,7 @@ static int __cifs_reconnect(struct TCP_Server_Info *server,
spin_unlock(&server->srv_lock);
cifs_swn_reset_server_dstaddr(server);
cifs_server_unlock(server);

/* increase ref count which reconnect work will drop */
spin_lock(&cifs_tcp_ses_lock);
server->srv_count++;
spin_unlock(&cifs_tcp_ses_lock);
if (mod_delayed_work(cifsiod_wq, &server->reconnect, 0))
cifs_put_tcp_session(server, false);
mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
}
} while (server->tcpStatus == CifsNeedReconnect);

Expand Down Expand Up @@ -538,13 +532,7 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)
spin_unlock(&server->srv_lock);
cifs_swn_reset_server_dstaddr(server);
cifs_server_unlock(server);

/* increase ref count which reconnect work will drop */
spin_lock(&cifs_tcp_ses_lock);
server->srv_count++;
spin_unlock(&cifs_tcp_ses_lock);
if (mod_delayed_work(cifsiod_wq, &server->reconnect, 0))
cifs_put_tcp_session(server, false);
mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
} while (server->tcpStatus == CifsNeedReconnect);

mutex_lock(&server->refpath_lock);
Expand Down Expand Up @@ -1620,25 +1608,22 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
list_del_init(&server->tcp_ses_list);
spin_unlock(&cifs_tcp_ses_lock);

/* For secondary channels, we pick up ref-count on the primary server */
if (SERVER_IS_CHAN(server))
cifs_put_tcp_session(server->primary_server, from_reconnect);

cancel_delayed_work_sync(&server->echo);

if (from_reconnect) {
if (from_reconnect)
/*
* Avoid deadlock here: reconnect work calls
* cifs_put_tcp_session() at its end. Need to be sure
* that reconnect work does nothing with server pointer after
* that step.
*/
if (cancel_delayed_work(&server->reconnect))
cifs_put_tcp_session(server, from_reconnect);
} else {
if (cancel_delayed_work_sync(&server->reconnect))
cifs_put_tcp_session(server, from_reconnect);
}
cancel_delayed_work(&server->reconnect);
else
cancel_delayed_work_sync(&server->reconnect);

/* For secondary channels, we pick up ref-count on the primary server */
if (SERVER_IS_CHAN(server))
cifs_put_tcp_session(server->primary_server, from_reconnect);

spin_lock(&server->srv_lock);
server->tcpStatus = CifsExiting;
Expand Down
2 changes: 2 additions & 0 deletions fs/smb/client/smb2ops.c
Expand Up @@ -2836,6 +2836,8 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses,
usleep_range(512, 2048);
} while (++retry_count < 5);

if (!rc && !dfs_rsp)
rc = -EIO;
if (rc) {
if (!is_retryable_error(rc) && rc != -ENOENT && rc != -EOPNOTSUPP)
cifs_tcon_dbg(VFS, "%s: ioctl error: rc=%d\n", __func__, rc);
Expand Down

0 comments on commit 2099306

Please sign in to comment.