Skip to content

Commit bd24d21

Browse files
joannekoongMiklos Szeredi
authored andcommitted
fuse: fix fuseblk i_blkbits for iomap partial writes
On regular fuse filesystems, i_blkbits is set to PAGE_SHIFT which means any iomap partial writes will mark the entire folio as uptodate. However fuseblk filesystems work differently and allow the blocksize to be less than the page size. As such, this may lead to data corruption if fuseblk sets its blocksize to less than the page size, uses the writeback cache, and does a partial write, then a read and the read happens before the write has undergone writeback, since the folio will not be marked uptodate from the partial write so the read will read in the entire folio from disk, which will overwrite the partial write. The long-term solution for this, which will also be needed for fuse to enable large folios with the writeback cache on, is to have fuse also use iomap for folio reads, but until that is done, the cleanest workaround is to use the page size for fuseblk's internal kernel inode blksize/blkbits values while maintaining current behavior for stat(). This was verified using ntfs-3g: $ sudo mkfs.ntfs -f -c 512 /dev/vdd1 $ sudo ntfs-3g /dev/vdd1 ~/fuseblk $ stat ~/fuseblk/hi.txt IO Block: 512 Fixes: a4c9ab1 ("fuse: use iomap for buffered writes") Signed-off-by: Joanne Koong <[email protected]> Reviewed-by: Darrick J. Wong <[email protected]> Signed-off-by: Miklos Szeredi <[email protected]>
1 parent 7956994 commit bd24d21

File tree

3 files changed

+21
-2
lines changed

3 files changed

+21
-2
lines changed

fs/fuse/dir.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1199,7 +1199,7 @@ static void fuse_fillattr(struct mnt_idmap *idmap, struct inode *inode,
11991199
if (attr->blksize != 0)
12001200
blkbits = ilog2(attr->blksize);
12011201
else
1202-
blkbits = inode->i_sb->s_blocksize_bits;
1202+
blkbits = fc->blkbits;
12031203

12041204
stat->blksize = 1 << blkbits;
12051205
}

fs/fuse/fuse_i.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,14 @@ struct fuse_conn {
975975
/* Request timeout (in jiffies). 0 = no timeout */
976976
unsigned int req_timeout;
977977
} timeout;
978+
979+
/*
980+
* This is a workaround until fuse uses iomap for reads.
981+
* For fuseblk servers, this represents the blocksize passed in at
982+
* mount time and for regular fuse servers, this is equivalent to
983+
* inode->i_blkbits.
984+
*/
985+
u8 blkbits;
978986
};
979987

980988
/*

fs/fuse/inode.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
292292
if (attr->blksize)
293293
fi->cached_i_blkbits = ilog2(attr->blksize);
294294
else
295-
fi->cached_i_blkbits = inode->i_sb->s_blocksize_bits;
295+
fi->cached_i_blkbits = fc->blkbits;
296296

297297
/*
298298
* Don't set the sticky bit in i_mode, unless we want the VFS
@@ -1810,10 +1810,21 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
18101810
err = -EINVAL;
18111811
if (!sb_set_blocksize(sb, ctx->blksize))
18121812
goto err;
1813+
/*
1814+
* This is a workaround until fuse hooks into iomap for reads.
1815+
* Use PAGE_SIZE for the blocksize else if the writeback cache
1816+
* is enabled, buffered writes go through iomap and a read may
1817+
* overwrite partially written data if blocksize < PAGE_SIZE
1818+
*/
1819+
fc->blkbits = sb->s_blocksize_bits;
1820+
if (ctx->blksize != PAGE_SIZE &&
1821+
!sb_set_blocksize(sb, PAGE_SIZE))
1822+
goto err;
18131823
#endif
18141824
} else {
18151825
sb->s_blocksize = PAGE_SIZE;
18161826
sb->s_blocksize_bits = PAGE_SHIFT;
1827+
fc->blkbits = sb->s_blocksize_bits;
18171828
}
18181829

18191830
sb->s_subtype = ctx->subtype;

0 commit comments

Comments
 (0)