fuse: Fix O_DIRECT operations vs cached writeback misorder
The problem is: 1. write cached data to a file 2. read directly from the same file (via another fd) The 2nd operation may read stale data, i.e. the one that was in a file before the 1st op. Problem is in how fuse manages writeback. When direct op occurs the core kernel code calls filemap_write_and_wait to flush all the cached ops in flight. But fuse acks the writeback right after the ->writepages callback exits w/o waiting for the real write to happen. Thus the subsequent direct op proceeds while the real writeback is still in flight. This is a problem for backends that reorder operation. Fix this by making the fuse direct IO callback explicitly wait on the in-flight writeback to finish. Signed-off-by: Maxim Patlasov <MPatlasov@parallels.com> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
This commit is contained in:
parent
fe38d7df23
commit
ea8cd33390
|
@ -95,7 +95,7 @@ static ssize_t cuse_read(struct file *file, char __user *buf, size_t count,
|
||||||
struct iovec iov = { .iov_base = buf, .iov_len = count };
|
struct iovec iov = { .iov_base = buf, .iov_len = count };
|
||||||
struct fuse_io_priv io = { .async = 0, .file = file };
|
struct fuse_io_priv io = { .async = 0, .file = file };
|
||||||
|
|
||||||
return fuse_direct_io(&io, &iov, 1, count, &pos, 0);
|
return fuse_direct_io(&io, &iov, 1, count, &pos, FUSE_DIO_CUSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t cuse_write(struct file *file, const char __user *buf,
|
static ssize_t cuse_write(struct file *file, const char __user *buf,
|
||||||
|
@ -109,7 +109,8 @@ static ssize_t cuse_write(struct file *file, const char __user *buf,
|
||||||
* No locking or generic_write_checks(), the server is
|
* No locking or generic_write_checks(), the server is
|
||||||
* responsible for locking and sanity checks.
|
* responsible for locking and sanity checks.
|
||||||
*/
|
*/
|
||||||
return fuse_direct_io(&io, &iov, 1, count, &pos, 1);
|
return fuse_direct_io(&io, &iov, 1, count, &pos,
|
||||||
|
FUSE_DIO_WRITE | FUSE_DIO_CUSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cuse_open(struct inode *inode, struct file *file)
|
static int cuse_open(struct inode *inode, struct file *file)
|
||||||
|
|
|
@ -358,12 +358,13 @@ u64 fuse_lock_owner_id(struct fuse_conn *fc, fl_owner_t id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if page is under writeback
|
* Check if any page in a range is under writeback
|
||||||
*
|
*
|
||||||
* This is currently done by walking the list of writepage requests
|
* This is currently done by walking the list of writepage requests
|
||||||
* for the inode, which can be pretty inefficient.
|
* for the inode, which can be pretty inefficient.
|
||||||
*/
|
*/
|
||||||
static bool fuse_page_is_writeback(struct inode *inode, pgoff_t index)
|
static bool fuse_range_is_writeback(struct inode *inode, pgoff_t idx_from,
|
||||||
|
pgoff_t idx_to)
|
||||||
{
|
{
|
||||||
struct fuse_conn *fc = get_fuse_conn(inode);
|
struct fuse_conn *fc = get_fuse_conn(inode);
|
||||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||||
|
@ -376,8 +377,8 @@ static bool fuse_page_is_writeback(struct inode *inode, pgoff_t index)
|
||||||
|
|
||||||
BUG_ON(req->inode != inode);
|
BUG_ON(req->inode != inode);
|
||||||
curr_index = req->misc.write.in.offset >> PAGE_CACHE_SHIFT;
|
curr_index = req->misc.write.in.offset >> PAGE_CACHE_SHIFT;
|
||||||
if (curr_index <= index &&
|
if (idx_from < curr_index + req->num_pages &&
|
||||||
index < curr_index + req->num_pages) {
|
curr_index <= idx_to) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -387,6 +388,11 @@ static bool fuse_page_is_writeback(struct inode *inode, pgoff_t index)
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool fuse_page_is_writeback(struct inode *inode, pgoff_t index)
|
||||||
|
{
|
||||||
|
return fuse_range_is_writeback(inode, index, index);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wait for page writeback to be completed.
|
* Wait for page writeback to be completed.
|
||||||
*
|
*
|
||||||
|
@ -1364,13 +1370,18 @@ static inline int fuse_iter_npages(const struct iov_iter *ii_p)
|
||||||
|
|
||||||
ssize_t fuse_direct_io(struct fuse_io_priv *io, const struct iovec *iov,
|
ssize_t fuse_direct_io(struct fuse_io_priv *io, const struct iovec *iov,
|
||||||
unsigned long nr_segs, size_t count, loff_t *ppos,
|
unsigned long nr_segs, size_t count, loff_t *ppos,
|
||||||
int write)
|
int flags)
|
||||||
{
|
{
|
||||||
|
int write = flags & FUSE_DIO_WRITE;
|
||||||
|
int cuse = flags & FUSE_DIO_CUSE;
|
||||||
struct file *file = io->file;
|
struct file *file = io->file;
|
||||||
|
struct inode *inode = file->f_mapping->host;
|
||||||
struct fuse_file *ff = file->private_data;
|
struct fuse_file *ff = file->private_data;
|
||||||
struct fuse_conn *fc = ff->fc;
|
struct fuse_conn *fc = ff->fc;
|
||||||
size_t nmax = write ? fc->max_write : fc->max_read;
|
size_t nmax = write ? fc->max_write : fc->max_read;
|
||||||
loff_t pos = *ppos;
|
loff_t pos = *ppos;
|
||||||
|
pgoff_t idx_from = pos >> PAGE_CACHE_SHIFT;
|
||||||
|
pgoff_t idx_to = (pos + count - 1) >> PAGE_CACHE_SHIFT;
|
||||||
ssize_t res = 0;
|
ssize_t res = 0;
|
||||||
struct fuse_req *req;
|
struct fuse_req *req;
|
||||||
struct iov_iter ii;
|
struct iov_iter ii;
|
||||||
|
@ -1384,6 +1395,14 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, const struct iovec *iov,
|
||||||
if (IS_ERR(req))
|
if (IS_ERR(req))
|
||||||
return PTR_ERR(req);
|
return PTR_ERR(req);
|
||||||
|
|
||||||
|
if (!cuse && fuse_range_is_writeback(inode, idx_from, idx_to)) {
|
||||||
|
if (!write)
|
||||||
|
mutex_lock(&inode->i_mutex);
|
||||||
|
fuse_sync_writes(inode);
|
||||||
|
if (!write)
|
||||||
|
mutex_unlock(&inode->i_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
while (count) {
|
while (count) {
|
||||||
size_t nres;
|
size_t nres;
|
||||||
fl_owner_t owner = current->files;
|
fl_owner_t owner = current->files;
|
||||||
|
@ -1472,7 +1491,8 @@ static ssize_t __fuse_direct_write(struct fuse_io_priv *io,
|
||||||
|
|
||||||
res = generic_write_checks(file, ppos, &count, 0);
|
res = generic_write_checks(file, ppos, &count, 0);
|
||||||
if (!res)
|
if (!res)
|
||||||
res = fuse_direct_io(io, iov, nr_segs, count, ppos, 1);
|
res = fuse_direct_io(io, iov, nr_segs, count, ppos,
|
||||||
|
FUSE_DIO_WRITE);
|
||||||
|
|
||||||
fuse_invalidate_attr(inode);
|
fuse_invalidate_attr(inode);
|
||||||
|
|
||||||
|
|
|
@ -868,9 +868,20 @@ int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid,
|
||||||
|
|
||||||
int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file,
|
int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file,
|
||||||
bool isdir);
|
bool isdir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fuse_direct_io() flags
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** If set, it is WRITE; otherwise - READ */
|
||||||
|
#define FUSE_DIO_WRITE (1 << 0)
|
||||||
|
|
||||||
|
/** CUSE pass fuse_direct_io() a file which f_mapping->host is not from FUSE */
|
||||||
|
#define FUSE_DIO_CUSE (1 << 1)
|
||||||
|
|
||||||
ssize_t fuse_direct_io(struct fuse_io_priv *io, const struct iovec *iov,
|
ssize_t fuse_direct_io(struct fuse_io_priv *io, const struct iovec *iov,
|
||||||
unsigned long nr_segs, size_t count, loff_t *ppos,
|
unsigned long nr_segs, size_t count, loff_t *ppos,
|
||||||
int write);
|
int flags);
|
||||||
long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
|
long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
|
||||||
unsigned int flags);
|
unsigned int flags);
|
||||||
long fuse_ioctl_common(struct file *file, unsigned int cmd,
|
long fuse_ioctl_common(struct file *file, unsigned int cmd,
|
||||||
|
|
Loading…
Reference in New Issue