Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 3 Apr 2019 02:29:56 +0000 (UTC)
From:      Alan Somers <asomers@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r345823 - in projects/fuse2: sys/fs/fuse tests/sys/fs/fusefs
Message-ID:  <201904030229.x332Tusu086697@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: asomers
Date: Wed Apr  3 02:29:56 2019
New Revision: 345823
URL: https://svnweb.freebsd.org/changeset/base/345823

Log:
  fusefs: during ftruncate, discard cached data past truncation point
  
  During truncate, fusefs was discarding entire cached blocks, but it wasn't
  zeroing out the unused portion of a final partial block.  This resulted in
  reads returning stale data.
  
  PR:		233783
  Reported by:	fsx
  Sponsored by:	The FreeBSD Foundation

Modified:
  projects/fuse2/sys/fs/fuse/fuse_node.c
  projects/fuse2/tests/sys/fs/fusefs/setattr.cc

Modified: projects/fuse2/sys/fs/fuse/fuse_node.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_node.c	Wed Apr  3 01:30:59 2019	(r345822)
+++ projects/fuse2/sys/fs/fuse/fuse_node.c	Wed Apr  3 02:29:56 2019	(r345823)
@@ -79,6 +79,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/fcntl.h>
 #include <sys/fnv_hash.h>
 #include <sys/priv.h>
+#include <sys/buf.h>
 #include <security/mac/mac_framework.h>
 #include <vm/vm.h>
 #include <vm/vm_extern.h>
@@ -413,17 +414,45 @@ fuse_vnode_setsize(struct vnode *vp, struct ucred *cre
 {
 	struct fuse_vnode_data *fvdat = VTOFUD(vp);
 	off_t oldsize;
+	size_t iosize;
+	struct buf *bp = NULL;
 	int err = 0;
 
 	ASSERT_VOP_ELOCKED(vp, "fuse_vnode_setsize");
 
+	iosize = fuse_iosize(vp);
 	oldsize = fvdat->filesize;
 	fvdat->filesize = newsize;
 	fvdat->flag |= FN_SIZECHANGE;
 
 	if (newsize < oldsize) {
+		daddr_t lbn;
+		size_t zsize;
+
 		err = vtruncbuf(vp, cred, newsize, fuse_iosize(vp));
+		if (err)
+			goto out;
+		if (newsize % iosize == 0)
+			goto out;
+		/* 
+		 * Zero the contents of the last partial block.
+		 * Sure seems like vtruncbuf should do this for us.
+		 */
+
+		lbn = newsize / iosize;
+		bp = getblk(vp, lbn, iosize, PCATCH, 0, 0);
+		if (!bp) {
+			err = EINTR;
+			goto out;
+		}
+		if (!(bp->b_flags & B_CACHE))
+			goto out;	/* Nothing to do */
+		zsize = (lbn + 1) * iosize - newsize;
+		bzero(bp->b_data + newsize - lbn * iosize, zsize);
 	}
+out:
+	if (bp)
+		brelse(bp);
 	vnode_pager_setsize(vp, newsize);
 	return err;
 }

Modified: projects/fuse2/tests/sys/fs/fusefs/setattr.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/setattr.cc	Wed Apr  3 01:30:59 2019	(r345822)
+++ projects/fuse2/tests/sys/fs/fusefs/setattr.cc	Wed Apr  3 02:29:56 2019	(r345823)
@@ -366,6 +366,116 @@ TEST_F(Setattr, truncate) {
 	EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
 }
 
+/*
+ * Truncating a file should discard cached data past the truncation point.
+ * This is a regression test for bug 233783.  The bug only applies when
+ * vfs.fusefs.data_cache_mode=1 or 2, but the test should pass regardless.
+ */
+TEST_F(Setattr, truncate_discards_cached_data) {
+	const char FULLPATH[] = "mountpoint/some_file.txt";
+	const char RELPATH[] = "some_file.txt";
+	void *w0buf, *rbuf, *expected;
+	off_t w0_offset = 0x1b8df;
+	size_t w0_size = 0x61e8;
+	off_t r_offset = 0xe1e6;
+	off_t r_size = 0xe229;
+	size_t trunc0_size = 0x10016;
+	size_t trunc1_size = 131072;
+	size_t cur_size = 0;
+	const uint64_t ino = 42;
+	mode_t mode = S_IFREG | 0644;
+	int fd;
+
+	w0buf = malloc(w0_size);
+	ASSERT_NE(NULL, w0buf) << strerror(errno);
+	memset(w0buf, 'X', w0_size);
+
+	rbuf = malloc(r_size);
+	ASSERT_NE(NULL, rbuf) << strerror(errno);
+
+	expected = malloc(r_size);
+	ASSERT_NE(NULL, expected) << strerror(errno);
+	memset(expected, 0, r_size);
+
+	expect_lookup(RELPATH, ino, mode, 0, 1);
+	expect_open(ino, O_RDWR, 1);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_GETATTR &&
+				in->header.nodeid == ino);
+		}, Eq(true)),
+		_)
+	).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto out) {
+		SET_OUT_HEADER_LEN(out, attr);
+		out->body.attr.attr.ino = ino;
+		out->body.attr.attr.mode = mode;
+		out->body.attr.attr.size = cur_size;
+	})));
+	/* 
+	 * The exact pattern of FUSE_WRITE operations depends on the setting of
+	 * vfs.fusefs.data_cache_mode.  But it's not important for this test.
+	 * Just set the mocks to accept anything
+	 */
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_WRITE);
+		}, Eq(true)),
+		_)
+	).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto out) {
+		SET_OUT_HEADER_LEN(out, write);
+		out->body.attr.attr.ino = ino;
+		out->body.write.size = in->body.write.size;
+		cur_size = std::max(cur_size,
+			in->body.write.size + in->body.write.offset);
+	})));
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_SETATTR &&
+				in->header.nodeid == ino &&
+				(in->body.setattr.valid & FATTR_SIZE));
+		}, Eq(true)),
+		_)
+	).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto out) {
+		auto trunc_size = in->body.setattr.size;
+		SET_OUT_HEADER_LEN(out, attr);
+		out->body.attr.attr.ino = ino;
+		out->body.attr.attr.mode = mode;
+		out->body.attr.attr.size = trunc_size;
+		cur_size = trunc_size;
+	})));
+
+	/* exact pattern of FUSE_READ depends on vfs.fusefs.data_cache_mode */
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_READ);
+		}, Eq(true)),
+		_)
+	).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto out) {
+		auto osize = std::min(cur_size - in->body.read.offset,
+			(size_t)in->body.read.size);
+		out->header.len = sizeof(struct fuse_out_header) + osize;
+		bzero(out->body.bytes, osize);
+	})));
+
+	fd = open(FULLPATH, O_RDWR, 0644);
+	ASSERT_LE(0, fd) << strerror(errno);
+
+	ASSERT_EQ((ssize_t)w0_size, pwrite(fd, w0buf, w0_size, w0_offset));
+	/* 1st truncate should discard cached data */
+	EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
+	/* 2nd truncate extends file into previously cached data */
+	EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
+	/* Read should return all zeros */
+	ASSERT_EQ((ssize_t)r_size, pread(fd, rbuf, r_size, r_offset));
+
+	ASSERT_EQ(0, memcmp(expected, rbuf, r_size));
+
+	free(expected);
+	free(rbuf);
+	free(w0buf);
+}
+
 /* Change a file's timestamps */
 TEST_F(Setattr, utimensat) {
 	const char FULLPATH[] = "mountpoint/some_file.txt";



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201904030229.x332Tusu086697>