diff options
author | Alan Somers <asomers@FreeBSD.org> | 2019-07-28 15:17:32 +0000 |
---|---|---|
committer | Alan Somers <asomers@FreeBSD.org> | 2019-07-28 15:17:32 +0000 |
commit | 669a092af1d423a2f2ae924dcf875cab73125c13 (patch) | |
tree | 15f608a5f2bcdd8f4a00ba182e3d47fd6d2fec2d /sys | |
parent | a63915c2d7ff177ce364488f86eff99949402051 (diff) | |
download | src-669a092af1d423a2f2ae924dcf875cab73125c13.tar.gz src-669a092af1d423a2f2ae924dcf875cab73125c13.zip |
fusefs: fix panic when writing with O_DIRECT and using writeback cache
When a fusefs file system is mounted using the writeback cache, the cache
may still be bypassed by opening a file with O_DIRECT. When writing with
O_DIRECT, the cache must be invalidated for the affected portion of the
file. Fix some panics caused by inadvertently invalidating too much.
Sponsored by: The FreeBSD Foundation
Notes
Notes:
svn path=/projects/fuse2/; revision=350388
Diffstat (limited to 'sys')
-rw-r--r-- | sys/fs/fuse/fuse_io.c | 61 |
1 files changed, 58 insertions, 3 deletions
diff --git a/sys/fs/fuse/fuse_io.c b/sys/fs/fuse/fuse_io.c index 00dc62834afc..924d437c2fe3 100644 --- a/sys/fs/fuse/fuse_io.c +++ b/sys/fs/fuse/fuse_io.c @@ -119,9 +119,11 @@ SDT_PROVIDER_DECLARE(fusefs); */ SDT_PROBE_DEFINE2(fusefs, , io, trace, "int", "char*"); +static int +fuse_inval_buf_range(struct vnode *vp, off_t filesize, off_t start, off_t end); static void fuse_io_clear_suid_on_write(struct vnode *vp, struct ucred *cred, - struct thread *td); + struct thread *td); static int fuse_read_directbackend(struct vnode *vp, struct uio *uio, struct ucred *cred, struct fuse_filehandle *fufh); @@ -136,6 +138,58 @@ static int fuse_write_biobackend(struct vnode *vp, struct uio *uio, struct ucred *cred, struct fuse_filehandle *fufh, int ioflag, pid_t pid); +/* Invalidate a range of cached data, whether dirty of not */ +static int +fuse_inval_buf_range(struct vnode *vp, off_t filesize, off_t start, off_t end) +{ + struct buf *bp; + daddr_t left_lbn, end_lbn, right_lbn; + off_t new_filesize; + int iosize, left_on, right_on, right_blksize; + + iosize = fuse_iosize(vp); + left_lbn = start / iosize; + end_lbn = howmany(end, iosize); + left_on = start & (iosize - 1); + if (left_on != 0) { + bp = getblk(vp, left_lbn, iosize, PCATCH, 0, 0); + if ((bp->b_flags & B_CACHE) != 0 && bp->b_dirtyend >= left_on) { + /* + * Flush the dirty buffer, because we don't have a + * byte-granular way to record which parts of the + * buffer are valid. + */ + bwrite(bp); + if (bp->b_error) + return (bp->b_error); + } else { + brelse(bp); + } + } + right_on = end & (iosize - 1); + if (right_on != 0) { + right_lbn = end / iosize; + new_filesize = MAX(filesize, end); + right_blksize = MIN(iosize, new_filesize - iosize * right_lbn); + bp = getblk(vp, right_lbn, right_blksize, PCATCH, 0, 0); + if ((bp->b_flags & B_CACHE) != 0 && bp->b_dirtyoff < right_on) { + /* + * Flush the dirty buffer, because we don't have a + * byte-granular way to record which parts of the + * buffer are valid. + */ + bwrite(bp); + if (bp->b_error) + return (bp->b_error); + } else { + brelse(bp); + } + } + + v_inval_buf_range(vp, left_lbn, end_lbn, iosize); + return (0); +} + /* * FreeBSD clears the SUID and SGID bits on any write by a non-root user. */ @@ -236,7 +290,6 @@ fuse_io_dispatch(struct vnode *vp, struct uio *uio, int ioflag, case UIO_WRITE: fuse_vnode_update(vp, FN_MTIMECHANGE | FN_CTIMECHANGE); if (directio) { - const int iosize = fuse_iosize(vp); off_t start, end, filesize; SDT_PROBE2(fusefs, , io, trace, 1, @@ -252,7 +305,9 @@ fuse_io_dispatch(struct vnode *vp, struct uio *uio, int ioflag, (IO_VMIO | IO_DIRECT), ("IO_DIRECT used for a cache flush?")); /* Invalidate the write cache when writing directly */ - v_inval_buf_range(vp, start, end, iosize); + err = fuse_inval_buf_range(vp, filesize, start, end); + if (err) + return (err); err = fuse_write_directbackend(vp, uio, cred, fufh, filesize, ioflag, false); } else { |