diff --git a/sys/fs/fuse/fuse_io.c b/sys/fs/fuse/fuse_io.c --- a/sys/fs/fuse/fuse_io.c +++ b/sys/fs/fuse/fuse_io.c @@ -303,6 +303,7 @@ struct fuse_write_out *fwo; struct fuse_dispatcher fdi; size_t chunksize; + ssize_t r; void *fwi_data; off_t as_written_offset; int diff; @@ -338,9 +339,11 @@ if (ioflag & IO_APPEND) uio_setoffset(uio, filesize); - err = vn_rlimit_fsize(vp, uio, uio->uio_td); - if (err != 0) + err = vn_rlimit_fsizex(vp, uio, 0, &r, uio->uio_td); + if (err != 0) { + vn_rlimit_fsizex_res(uio, r); return (err); + } fdisp_init(&fdi, 0); @@ -456,6 +459,7 @@ if (wrote_anything) fuse_vnode_undirty_cached_timestamps(vp, false); + vn_rlimit_fsizex_res(uio, r); return (err); } @@ -472,6 +476,7 @@ struct buf *bp; daddr_t lbn; off_t filesize; + ssize_t r; int bcount; int n, on, seqcount, err = 0; @@ -494,9 +499,11 @@ if (ioflag & IO_APPEND) uio_setoffset(uio, filesize); - err = vn_rlimit_fsize(vp, uio, uio->uio_td); - if (err != 0) + err = vn_rlimit_fsizex(vp, uio, 0, &r, uio->uio_td); + if (err != 0) { + vn_rlimit_fsizex_res(uio, r); return (err); + } do { bool direct_append, extending; @@ -724,6 +731,7 @@ break; } while (uio->uio_resid > 0 && n > 0); + vn_rlimit_fsizex_res(uio, r); return (err); } diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -2248,6 +2248,9 @@ case VREG: if (vfs_isrdonly(mp)) return (EROFS); + err = vn_rlimit_trunc(vap->va_size, td); + if (err) + return (err); break; default: /* diff --git a/tests/sys/fs/fusefs/setattr.cc b/tests/sys/fs/fusefs/setattr.cc --- a/tests/sys/fs/fusefs/setattr.cc +++ b/tests/sys/fs/fusefs/setattr.cc @@ -31,10 +31,14 @@ */ extern "C" { +#include +#include #include +#include #include #include +#include } #include "mockfs.hh" @@ -42,11 +46,15 @@ using namespace testing; -class Setattr : public FuseTest {}; +class Setattr : public FuseTest { +public: +static sig_atomic_t s_sigxfsz; +}; class RofsSetattr: public Setattr { public: virtual void SetUp() { + s_sigxfsz = 0; m_ro = true; Setattr::SetUp(); } @@ -61,6 +69,12 @@ }; +sig_atomic_t Setattr::s_sigxfsz = 0; + +void sigxfsz_handler(int __unused sig) { + Setattr::s_sigxfsz = 1; +} + /* * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs * should use the cached attributes, rather than query the daemon @@ -553,6 +567,34 @@ leak(fd); } +/* truncate should fail if it would cause the file to exceed RLIMIT_FSIZE */ +TEST_F(Setattr, truncate_rlimit_rsize) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + struct rlimit rl; + const uint64_t ino = 42; + const uint64_t oldsize = 0; + const uint64_t newsize = 100'000'000; + + EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) + .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, entry); + out.body.entry.attr.mode = S_IFREG | 0644; + out.body.entry.nodeid = ino; + out.body.entry.attr.size = oldsize; + }))); + + rl.rlim_cur = newsize / 2; + rl.rlim_max = 10 * newsize; + ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); + ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); + + EXPECT_EQ(-1, truncate(FULLPATH, newsize)); + EXPECT_EQ(EFBIG, errno); + EXPECT_EQ(1, s_sigxfsz); +} + /* Change a file's timestamps */ TEST_F(Setattr, utimensat) { const char FULLPATH[] = "mountpoint/some_file.txt"; 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 @@ -52,10 +52,7 @@ class Write: public FuseTest { public: -static sig_atomic_t s_sigxfsz; - void SetUp() { - s_sigxfsz = 0; FuseTest::SetUp(); } @@ -118,8 +115,6 @@ }; -sig_atomic_t Write::s_sigxfsz = 0; - class Write_7_8: public FuseTest { public: @@ -211,8 +206,28 @@ class WriteEofDuringVnopStrategy: public Write, public WithParamInterface {}; +class WriteRlimitFsize: public Write, public WithParamInterface { +public: +static sig_atomic_t s_sigxfsz; +struct rlimit m_initial_limit; + +void SetUp() { + s_sigxfsz = 0; + getrlimit(RLIMIT_FSIZE, &m_initial_limit); + FuseTest::SetUp(); +} + +void TearDown() { + setrlimit(RLIMIT_FSIZE, &m_initial_limit); + + FuseTest::TearDown(); +} +}; + +sig_atomic_t WriteRlimitFsize::s_sigxfsz = 0; + void sigxfsz_handler(int __unused sig) { - Write::s_sigxfsz = 1; + WriteRlimitFsize::s_sigxfsz = 1; } /* AIO writes need to set the header's pid field correctly */ @@ -531,7 +546,7 @@ } /* fusefs should respect RLIMIT_FSIZE */ -TEST_F(Write, rlimit_fsize) +TEST_P(WriteRlimitFsize, rlimit_fsize) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; @@ -540,17 +555,19 @@ ssize_t bufsize = strlen(CONTENTS); off_t offset = 1'000'000'000; uint64_t ino = 42; - int fd; + int fd, oflag; + + oflag = GetParam(); expect_lookup(RELPATH, ino, 0); expect_open(ino, 0, 1); rl.rlim_cur = offset; - rl.rlim_max = 10 * offset; + rl.rlim_max = m_initial_limit.rlim_max; ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); - fd = open(FULLPATH, O_WRONLY); + fd = open(FULLPATH, O_WRONLY | oflag); ASSERT_LE(0, fd) << strerror(errno); @@ -560,6 +577,47 @@ leak(fd); } +/* + * When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not + * aborted. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=164793 + */ +TEST_P(WriteRlimitFsize, rlimit_fsize_truncate) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz"; + struct rlimit rl; + ssize_t bufsize = strlen(CONTENTS); + uint64_t ino = 42; + off_t offset = 1 << 30; + off_t limit = offset + strlen(CONTENTS) / 2; + int fd, oflag; + + oflag = GetParam(); + + expect_lookup(RELPATH, ino, 0); + expect_open(ino, 0, 1); + expect_write(ino, offset, bufsize / 2, bufsize / 2, CONTENTS); + + rl.rlim_cur = limit; + rl.rlim_max = m_initial_limit.rlim_max; + ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); + ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); + + fd = open(FULLPATH, O_WRONLY | oflag); + + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize / 2, pwrite(fd, CONTENTS, bufsize, offset)) + << strerror(errno); + leak(fd); +} + +INSTANTIATE_TEST_CASE_P(W, WriteRlimitFsize, + Values(0, O_DIRECT) +); + /* * A short read indicates EOF. Test that nothing bad happens if we get EOF * during the R of a RMW operation.