Commit dba9f997 authored by Paulo Alcantara's avatar Paulo Alcantara Committed by Steve French
Browse files

smb: client: fix race with fallocate(2) and AIO+DIO



AIO+DIO may extend the file size, hence we need to make sure ->i_size
is stable across the entire fallocate(2) operation, otherwise it would
become a truncate and then inode size reduced back down when it
finishes.

Fix this by calling netfs_wait_for_outstanding_io() right after
acquiring ->i_rwsem exclusively in cifs_fallocate() and then guarantee
a stable ->i_size across fallocate(2).

Also call netfs_wait_for_outstanding_io() after truncating pagecache
to avoid any potential races with writeback.

Signed-off-by: default avatarPaulo Alcantara (Red Hat) <pc@manguebit.org>
Reviewed-by: default avatarDavid Howells <dhowells@redhat.com>
Fixes: 210627b0aca9 ("smb: client: fix missing timestamp updates with O_TRUNC")
Cc: Frank Sorenson <sorenson@redhat.com>
Cc: linux-cifs@vger.kernel.org
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent b95cd1bd
Loading
Loading
Loading
Loading
+19 −3
Original line number Diff line number Diff line
@@ -392,11 +392,27 @@ static long cifs_fallocate(struct file *file, int mode, loff_t off, loff_t len)
	struct cifs_sb_info *cifs_sb = CIFS_FILE_SB(file);
	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
	struct TCP_Server_Info *server = tcon->ses->server;
	struct inode *inode = file_inode(file);
	int rc;

	if (server->ops->fallocate)
		return server->ops->fallocate(file, tcon, mode, off, len);

	if (!server->ops->fallocate)
		return -EOPNOTSUPP;

	rc = inode_lock_killable(inode);
	if (rc)
		return rc;

	netfs_wait_for_outstanding_io(inode);

	rc = file_modified(file);
	if (rc)
		goto out_unlock;

	rc = server->ops->fallocate(file, tcon, mode, off, len);

out_unlock:
	inode_unlock(inode);
	return rc;
}

static int cifs_permission(struct mnt_idmap *idmap,
+1 −0
Original line number Diff line number Diff line
@@ -3012,6 +3012,7 @@ void cifs_setsize(struct inode *inode, loff_t offset)
	spin_unlock(&inode->i_lock);
	inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
	truncate_pagecache(inode, offset);
	netfs_wait_for_outstanding_io(inode);
}

int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
+6 −12
Original line number Diff line number Diff line
@@ -3367,7 +3367,6 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,
	trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid,
			      ses->Suid, offset, len);

	inode_lock(inode);
	filemap_invalidate_lock(inode->i_mapping);

	i_size = i_size_read(inode);
@@ -3385,6 +3384,7 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,
	 * first, otherwise the data may be inconsistent with the server.
	 */
	truncate_pagecache_range(inode, offset, offset + len - 1);
	netfs_wait_for_outstanding_io(inode);

	/* if file not oplocked can't be sure whether asking to extend size */
	rc = -EOPNOTSUPP;
@@ -3413,7 +3413,6 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon,

 zero_range_exit:
	filemap_invalidate_unlock(inode->i_mapping);
	inode_unlock(inode);
	free_xid(xid);
	if (rc)
		trace_smb3_zero_err(xid, cfile->fid.persistent_fid, tcon->tid,
@@ -3437,7 +3436,6 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,

	xid = get_xid();

	inode_lock(inode);
	/* Need to make file sparse, if not already, before freeing range. */
	/* Consider adding equivalent for compressed since it could also work */
	if (!smb2_set_sparse(xid, tcon, cfile, inode, set_sparse)) {
@@ -3451,6 +3449,7 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
	 * caches first, otherwise the data may be inconsistent with the server.
	 */
	truncate_pagecache_range(inode, offset, offset + len - 1);
	netfs_wait_for_outstanding_io(inode);

	cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len);

@@ -3485,7 +3484,6 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
unlock:
	filemap_invalidate_unlock(inode->i_mapping);
out:
	inode_unlock(inode);
	free_xid(xid);
	return rc;
}
@@ -3749,8 +3747,6 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,

	xid = get_xid();

	inode_lock(inode);

	old_eof = i_size_read(inode);
	if ((off >= old_eof) ||
	    off + len >= old_eof) {
@@ -3765,6 +3761,7 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,

	truncate_pagecache_range(inode, off, old_eof);
	ictx->zero_point = old_eof;
	netfs_wait_for_outstanding_io(inode);

	rc = smb2_copychunk_range(xid, cfile, cfile, off + len,
				  old_eof - off - len, off);
@@ -3786,7 +3783,6 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon,
out_2:
	filemap_invalidate_unlock(inode->i_mapping);
out:
	inode_unlock(inode);
	free_xid(xid);
	return rc;
}
@@ -3803,8 +3799,6 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon,

	xid = get_xid();

	inode_lock(inode);

	old_eof = i_size_read(inode);
	if (off >= old_eof) {
		rc = -EINVAL;
@@ -3819,6 +3813,7 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon,
	if (rc < 0)
		goto out_2;
	truncate_pagecache_range(inode, off, old_eof);
	netfs_wait_for_outstanding_io(inode);

	rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
			  cfile->fid.volatile_fid, cfile->pid, new_eof);
@@ -3842,7 +3837,6 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon,
out_2:
	filemap_invalidate_unlock(inode->i_mapping);
out:
	inode_unlock(inode);
	free_xid(xid);
	return rc;
}