fusefs: fix a bug with WriteBack cacheing

An errant vfs_bio_clrbuf snuck in in r348931.  Surprisingly, it doesn't have
any effect most of the time.  But under some circumstances it cause the
buffer to behave in a write-only fashion.

Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Alan Somers 2019-06-13 19:07:03 +00:00
parent 93c0c1d4ce
commit dff3a6b410
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=349021
2 changed files with 76 additions and 14 deletions

View File

@ -267,7 +267,7 @@ fuse_io_dispatch(struct vnode *vp, struct uio *uio, int ioflag, bool pages,
}
SDT_PROBE_DEFINE4(fusefs, , io, read_bio_backend_start, "int", "int", "int", "int");
SDT_PROBE_DEFINE2(fusefs, , io, read_bio_backend_feed, "int", "int");
SDT_PROBE_DEFINE2(fusefs, , io, read_bio_backend_feed, "int", "struct buf*");
SDT_PROBE_DEFINE4(fusefs, , io, read_bio_backend_end, "int", "ssize_t", "int",
"struct buf*");
static int
@ -330,8 +330,7 @@ fuse_read_biobackend(struct vnode *vp, struct uio *uio, int ioflag,
if (on < bcount)
n = MIN((unsigned)(bcount - on), uio->uio_resid);
if (n > 0) {
SDT_PROBE2(fusefs, , io, read_bio_backend_feed,
n, n + (int)bp->b_resid);
SDT_PROBE2(fusefs, , io, read_bio_backend_feed, n, bp);
err = uiomove(bp->b_data + on, n, uio);
}
vfs_bio_brelse(bp, ioflag);
@ -344,8 +343,8 @@ fuse_read_biobackend(struct vnode *vp, struct uio *uio, int ioflag,
SDT_PROBE_DEFINE1(fusefs, , io, read_directbackend_start,
"struct fuse_read_in*");
SDT_PROBE_DEFINE2(fusefs, , io, read_directbackend_complete,
"struct fuse_dispatcher*", "struct uio*");
SDT_PROBE_DEFINE3(fusefs, , io, read_directbackend_complete,
"struct fuse_dispatcher*", "struct fuse_read_in*", "struct uio*");
static int
fuse_read_directbackend(struct vnode *vp, struct uio *uio,
@ -390,8 +389,8 @@ fuse_read_directbackend(struct vnode *vp, struct uio *uio,
if ((err = fdisp_wait_answ(&fdi)))
goto out;
SDT_PROBE2(fusefs, , io, read_directbackend_complete,
fdi.iosize, uio);
SDT_PROBE3(fusefs, , io, read_directbackend_complete,
&fdi, fri, uio);
if ((err = uiomove(fdi.answ, MIN(fri->size, fdi.iosize), uio)))
break;
@ -555,6 +554,7 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio,
SDT_PROBE_DEFINE6(fusefs, , io, write_biobackend_start, "int64_t", "int", "int",
"struct uio*", "int", "bool");
SDT_PROBE_DEFINE2(fusefs, , io, write_biobackend_append_race, "long", "int");
SDT_PROBE_DEFINE2(fusefs, , io, write_biobackend_issue, "int", "struct buf*");
static int
fuse_write_biobackend(struct vnode *vp, struct uio *uio,
@ -602,14 +602,14 @@ fuse_write_biobackend(struct vnode *vp, struct uio *uio,
again:
/* Get or create a buffer for the write */
direct_append = uio->uio_offset == filesize && n;
if ((off_t)lbn * biosize + on + n < filesize) {
if (uio->uio_offset + n < filesize) {
extending = false;
if ((off_t)(lbn + 1) * biosize < filesize) {
/* Not the file's last block */
bcount = biosize;
} else {
/* The file's last block */
bcount = filesize - (off_t)lbn *biosize;
bcount = filesize - (off_t)lbn * biosize;
}
} else {
extending = true;
@ -650,8 +650,6 @@ fuse_write_biobackend(struct vnode *vp, struct uio *uio,
break;
}
}
if (biosize > bcount)
vfs_bio_clrbuf(bp);
SDT_PROBE6(fusefs, , io, write_biobackend_start,
lbn, on, n, uio, bcount, direct_append);
@ -733,6 +731,7 @@ fuse_write_biobackend(struct vnode *vp, struct uio *uio,
* reasons: the only way to know if a write is valid
* if its actually written out.)
*/
SDT_PROBE2(fusefs, , io, write_biobackend_issue, 0, bp);
bwrite(bp);
if (bp->b_error == EINTR) {
err = EINTR;
@ -780,23 +779,33 @@ fuse_write_biobackend(struct vnode *vp, struct uio *uio,
* already-written page whenever extending a file with
* ftruncate or another write.
*/
SDT_PROBE2(fusefs, , io, write_biobackend_issue, 1, bp);
err = bwrite(bp);
} else if (ioflag & IO_SYNC) {
SDT_PROBE2(fusefs, , io, write_biobackend_issue, 2, bp);
err = bwrite(bp);
} else if (vm_page_count_severe() ||
buf_dirty_count_severe() ||
(ioflag & IO_ASYNC)) {
/* TODO: enable write clustering later */
SDT_PROBE2(fusefs, , io, write_biobackend_issue, 3, bp);
bawrite(bp);
} else if (on == 0 && n == bcount) {
if ((vp->v_mount->mnt_flag & MNT_NOCLUSTERW) == 0)
if ((vp->v_mount->mnt_flag & MNT_NOCLUSTERW) == 0) {
SDT_PROBE2(fusefs, , io, write_biobackend_issue,
4, bp);
bdwrite(bp);
else
} else {
SDT_PROBE2(fusefs, , io, write_biobackend_issue,
5, bp);
bawrite(bp);
}
} else if (ioflag & IO_DIRECT) {
SDT_PROBE2(fusefs, , io, write_biobackend_issue, 6, bp);
bawrite(bp);
} else {
bp->b_flags &= ~B_CLUSTEROK;
SDT_PROBE2(fusefs, , io, write_biobackend_issue, 7, bp);
bdwrite(bp);
}
if (err)

View File

@ -51,6 +51,27 @@ const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
static void compare(const void *tbuf, const void *controlbuf, off_t baseofs,
ssize_t size)
{
int i;
for (i = 0; i < size; i++) {
if (((const char*)tbuf)[i] != ((const char*)controlbuf)[i]) {
off_t ofs = baseofs + i;
FAIL() << "miscompare at offset "
<< std::hex
<< std::showbase
<< ofs
<< ". expected = "
<< std::setw(2)
<< (unsigned)((const uint8_t*)controlbuf)[i]
<< " got = "
<< (unsigned)((const uint8_t*)tbuf)[i];
}
}
}
class Io: public FuseTest {
public:
int m_backing_fd, m_control_fd, m_test_fd;
@ -171,7 +192,7 @@ void do_read(ssize_t size, off_t offs)
ASSERT_EQ(size, pread(m_control_fd, control_buf, size, offs))
<< strerror(errno);
ASSERT_EQ(0, memcmp(test_buf, control_buf, size));
compare(test_buf, control_buf, offs, size);
free(control_buf);
free(test_buf);
@ -308,3 +329,35 @@ TEST_F(Io, truncate_into_dirty_buffer2)
do_ftruncate(truncsize2);
close(m_test_fd);
}
/*
* Regression test for a bug introduced in r348931
*
* Sequence of operations:
* 1) The first write reads lbn so it can modify it
* 2) The first write flushes lbn 3 immediately because it's the end of file
* 3) The first write then flushes lbn 4 because it's the end of the file
* 4) The second write modifies the cached versions of lbn 3 and 4
* 5) The third write's getblkx invalidates lbn 4's B_CACHE because it's
* extending the buffer. Then it flushes lbn 4 because B_DELWRI was set but
* B_CACHE was clear.
* 6) fuse_write_biobackend erroneously called vfs_bio_clrbuf, putting the
* buffer into a weird write-only state. All read operations would return
* 0. Writes were apparently still processed, because the buffer's contents
* were correct when examined in a core dump.
* 7) The third write reads lbn 4 because cache is clear
* 9) uiomove dutifully copies new data into the buffer
* 10) The buffer's dirty is flushed to lbn 4
* 11) The read returns all zeros because of step 6.
*
* Based on:
* fsx -WR -l 524388 -o 131072 -P /tmp -S6456 -q fsx.bin
*/
TEST_F(Io, resize_a_valid_buffer_while_extending)
{
do_write(0x14530, 0x36ee6); /* [0x36ee6, 0x4b415] */
do_write(0x1507c, 0x33256); /* [0x33256, 0x482d1] */
do_write(0x175c, 0x4c03d); /* [0x4c03d, 0x4d798] */
do_read(0xe277, 0x3599c); /* [0x3599c, 0x43c12] */
close(m_test_fd);
}