Commit fa30a81f authored by Steve French's avatar Steve French
Browse files

smb3: fix temporary data corruption in collapse range



collapse range doesn't discard the affected cached region
so can risk temporarily corrupting the file data. This
fixes xfstest generic/031

I also decided to merge a minor cleanup to this into the same patch
(avoiding rereading inode size repeatedly unnecessarily) to make it
clearer.

Cc: stable@vger.kernel.org
Fixes: 5476b5dd ("cifs: add support for FALLOC_FL_COLLAPSE_RANGE")
Reported-by: default avatarDavid Howells <dhowells@redhat.com>
Tested-by: default avatarDavid Howells <dhowells@redhat.com>
Reviewed-by: default avatarDavid Howells <dhowells@redhat.com>
cc: Ronnie Sahlberg <lsahlber@redhat.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent c3a72bb2
Loading
Loading
Loading
Loading
+16 −10
Original line number Diff line number Diff line
@@ -3669,41 +3669,47 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,
{
	int rc;
	unsigned int xid;
	struct inode *inode;
	struct inode *inode = file_inode(file);
	struct cifsFileInfo *cfile = file->private_data;
	struct cifsInodeInfo *cifsi;
	struct cifsInodeInfo *cifsi = CIFS_I(inode);
	__le64 eof;
	loff_t old_eof;

	xid = get_xid();

	inode = d_inode(cfile->dentry);
	cifsi = CIFS_I(inode);
	inode_lock(inode);

	if (off >= i_size_read(inode) ||
	    off + len >= i_size_read(inode)) {
	old_eof = i_size_read(inode);
	if ((off >= old_eof) ||
	    off + len >= old_eof) {
		rc = -EINVAL;
		goto out;
	}

	filemap_invalidate_lock(inode->i_mapping);
	filemap_write_and_wait(inode->i_mapping);
	truncate_pagecache_range(inode, off, old_eof);

	rc = smb2_copychunk_range(xid, cfile, cfile, off + len,
				  i_size_read(inode) - off - len, off);
				  old_eof - off - len, off);
	if (rc < 0)
		goto out;
		goto out_2;

	eof = cpu_to_le64(i_size_read(inode) - len);
	eof = cpu_to_le64(old_eof - len);
	rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
			  cfile->fid.volatile_fid, cfile->pid, &eof);
	if (rc < 0)
		goto out;
		goto out_2;

	rc = 0;

	cifsi->server_eof = i_size_read(inode) - len;
	truncate_setsize(inode, cifsi->server_eof);
	fscache_resize_cookie(cifs_inode_cookie(inode), cifsi->server_eof);
out_2:
	filemap_invalidate_unlock(inode->i_mapping);
 out:
	inode_unlock(inode);
	free_xid(xid);
	return rc;
}