Page MenuHomeFreeBSD

D55047.id182275.diff
No OneTemporary

D55047.id182275.diff

diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c
--- a/sys/fs/fuse/fuse_internal.c
+++ b/sys/fs/fuse/fuse_internal.c
@@ -257,6 +257,7 @@
struct fuse_vnode_data *fvdat;
struct fuse_data *data;
struct vattr *vp_cache_at;
+ off_t size;
mp = vnode_mount(vp);
fvdat = VTOFUD(vp);
@@ -264,13 +265,25 @@
ASSERT_CACHED_ATTRS_LOCKED(vp);
+ size = attr->size;
+
fuse_validity_2_bintime(attr_valid, attr_valid_nsec,
&fvdat->attr_cache_timeout);
+ /*
+ * Check if the file has been changed from under us. We
+ * can only reliably check whether the server has grown
+ * the file unilaterally, not shrunk. This is because
+ * unflushed writes on our end can lead us to report
+ * larger sizes compared to the result of a getattr
+ * to the server even if we have set FN_SIZECHANGE.
+ * The opposite isn't true, because we clear the
+ * FN_SIZECHANGE flag if we truncate the file.
+ */
if (vnode_isreg(vp) &&
fvdat->cached_attrs.va_size != VNOVAL &&
fvdat->flag & FN_SIZECHANGE &&
- attr->size != fvdat->cached_attrs.va_size)
+ attr->size > fvdat->cached_attrs.va_size)
{
if (data->cache_mode == FUSE_CACHE_WB)
{
@@ -313,11 +326,16 @@
}
/* Fix our buffers if the filesize changed without us knowing */
- if (vnode_isreg(vp) && attr->size != fvdat->cached_attrs.va_size) {
+ if (vnode_isreg(vp) && attr->size != fvdat->cached_attrs.va_size &&
+ !(fvdat->flag & FN_SIZECHANGE)) {
(void)fuse_vnode_setsize(vp, attr->size, from_server);
fvdat->cached_attrs.va_size = attr->size;
}
+ /* If we have a local dirty copy of the size, use it. */
+ if (fvdat->flag & FN_SIZECHANGE && fvdat->cached_attrs.va_size != VNOVAL)
+ size = fvdat->cached_attrs.va_size;
+
if (attr_valid > 0 || attr_valid_nsec > 0)
vp_cache_at = &(fvdat->cached_attrs);
else if (vap != NULL)
@@ -332,7 +350,7 @@
vp_cache_at->va_uid = attr->uid;
vp_cache_at->va_gid = attr->gid;
vp_cache_at->va_rdev = attr->rdev;
- vp_cache_at->va_size = attr->size;
+ vp_cache_at->va_size = size;
/* XXX on i386, seconds are truncated to 32 bits */
vp_cache_at->va_atime.tv_sec = attr->atime;
vp_cache_at->va_atime.tv_nsec = attr->atimensec;
diff --git a/tests/sys/fs/fusefs/write.cc b/tests/sys/fs/fusefs/write.cc
--- a/tests/sys/fs/fusefs/write.cc
+++ b/tests/sys/fs/fusefs/write.cc
@@ -1532,6 +1532,68 @@
leak(fd);
}
+/*
+ * When deciding whether to report the cached file size or that of the server, FUSE
+ * can report either the cached value or a new value from the server. If the file
+ * has been modified by a write locally that has not been flushed to the server, it
+ * should pick the local cached value. Ensure that the local file size takes precedence
+ * then over the server's (the alternative would trigger spurious file truncations).
+ */
+TEST_F(WriteBackAsync, lookup_dont_clobber_pending_size)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const char *CONTENTS = "abcdefgh";
+ ssize_t bufsize = strlen(CONTENTS);
+ uint64_t ino = 42;
+ uint64_t server_size = 0;
+ mode_t mode = S_IFREG | 0644;
+ int fd;
+ struct stat sb;
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
+ .WillRepeatedly(Invoke(
+ ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = mode;
+ out.body.entry.nodeid = ino;
+ out.body.entry.attr.nlink = 1;
+ out.body.entry.attr.size = server_size;
+ out.body.entry.attr_valid = UINT64_MAX;
+ })));
+ expect_open(ino, 0, 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_valid = UINT64_MAX;
+ out.body.attr.attr.size = server_size;
+ })));
+
+ fd = open(FULLPATH, O_RDWR);
+ ASSERT_LE(0, fd) << strerror(errno);
+
+ /* Extend the file. */
+ ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
+
+ /* If the write and stat are in the same tick the test doesn't trigger. */
+ usleep(10000);
+
+ /* Do another getattr call and ensure the file wasn't clobbered. */
+ ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
+ EXPECT_EQ(bufsize, sb.st_size);
+
+ leak(fd);
+}
+
/* Any dirty timestamp fields should be flushed during a SETATTR */
TEST_F(WriteBackAsync, timestamps_during_setattr)
{

File Metadata

Mime Type
text/plain
Expires
Sat, Jul 25, 8:36 AM (13 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
35468909
Default Alt Text
D55047.id182275.diff (4 KB)

Event Timeline