diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index 5df6e18444ca..68ab4c5b0794 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -934,8 +934,8 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio) * redundant with max_write */ /* - * max_background, congestion_threshold, and time_gran - * are not implemented + * max_background and congestion_threshold are not + * implemented */ } else { err = EINVAL; @@ -956,6 +956,12 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio) fsess_set_notimpl(data->mp, FUSE_DESTROY); } + if (fuse_libabi_geq(data, 7, 23) && fiio->time_gran >= 1 && + fiio->time_gran <= 1000000000) + data->time_gran = fiio->time_gran; + else + data->time_gran = 1; + out: if (err) { fdata_set_dead(data); diff --git a/sys/fs/fuse/fuse_ipc.h b/sys/fs/fuse/fuse_ipc.h index 0f9b1180f362..810792fc9c8e 100644 --- a/sys/fs/fuse/fuse_ipc.h +++ b/sys/fs/fuse/fuse_ipc.h @@ -206,6 +206,7 @@ struct fuse_data { struct selinfo ks_rsel; int daemon_timeout; + unsigned time_gran; uint64_t notimpl; uint64_t mnt_flag; }; diff --git a/sys/fs/fuse/fuse_node.c b/sys/fs/fuse/fuse_node.c index d5429e905f0c..7a0774a19778 100644 --- a/sys/fs/fuse/fuse_node.c +++ b/sys/fs/fuse/fuse_node.c @@ -455,10 +455,14 @@ void fuse_vnode_update(struct vnode *vp, int flags) { struct fuse_vnode_data *fvdat = VTOFUD(vp); + struct fuse_data *data = fuse_get_mpdata(vnode_mount(vp)); struct timespec ts; vfs_timestamp(&ts); + if (data->time_gran > 1) + ts.tv_nsec = rounddown(ts.tv_nsec, data->time_gran); + if (flags & FN_MTIMECHANGE) fvdat->cached_attrs.va_mtime = ts; if (flags & FN_CTIMECHANGE) diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc index 0584b43db482..e4f28b9563d0 100644 --- a/tests/sys/fs/fusefs/mockfs.cc +++ b/tests/sys/fs/fusefs/mockfs.cc @@ -348,7 +348,7 @@ void MockFS::debug_response(const mockfs_buf_out &out) { MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions, bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags, uint32_t kernel_minor_version, uint32_t max_write, bool async, - bool noclusterr) + bool noclusterr, unsigned time_gran) { struct sigaction sa; struct iovec *iov = NULL; @@ -362,6 +362,7 @@ MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions, m_maxwrite = max_write; m_nready = -1; m_pm = pm; + m_time_gran = time_gran; m_quit = false; if (m_pm == KQ) m_kq = kqueue(); @@ -475,6 +476,7 @@ void MockFS::init(uint32_t flags) { if (m_kernel_minor_version < 23) { SET_OUT_HEADER_LEN(*out, init_7_22); } else { + out->body.init.time_gran = m_time_gran; SET_OUT_HEADER_LEN(*out, init); } diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh index e8bd09df1c8d..91bb248c6f80 100644 --- a/tests/sys/fs/fusefs/mockfs.hh +++ b/tests/sys/fs/fusefs/mockfs.hh @@ -277,6 +277,9 @@ class MockFS { /* Method the daemon should use for I/O to and from /dev/fuse */ enum poll_method m_pm; + /* Timestamp granularity in nanoseconds */ + unsigned m_time_gran; + void debug_request(const mockfs_buf_in&); void debug_response(const mockfs_buf_out&); @@ -320,7 +323,7 @@ class MockFS { bool default_permissions, bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags, uint32_t kernel_minor_version, uint32_t max_write, bool async, - bool no_clusterr); + bool no_clusterr, unsigned time_gran); virtual ~MockFS(); diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc index eda43a5dcbf5..cca5d3582c2c 100644 --- a/tests/sys/fs/fusefs/utils.cc +++ b/tests/sys/fs/fusefs/utils.cc @@ -117,7 +117,7 @@ void FuseTest::SetUp() { m_mock = new MockFS(m_maxreadahead, m_allow_other, m_default_permissions, m_push_symlinks_in, m_ro, m_pm, m_init_flags, m_kernel_minor_version, - m_maxwrite, m_async, m_noclusterr); + m_maxwrite, m_async, m_noclusterr, m_time_gran); /* * FUSE_ACCESS is called almost universally. Expecting it in * each test case would be super-annoying. Instead, set a diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh index 174ed0e71f7c..2dbca6808b07 100644 --- a/tests/sys/fs/fusefs/utils.hh +++ b/tests/sys/fs/fusefs/utils.hh @@ -55,6 +55,7 @@ class FuseTest : public ::testing::Test { bool m_ro; bool m_async; bool m_noclusterr; + unsigned m_time_gran; MockFS *m_mock = NULL; const static uint64_t FH = 0xdeadbeef1a7ebabe; @@ -73,7 +74,8 @@ class FuseTest : public ::testing::Test { m_push_symlinks_in(false), m_ro(false), m_async(false), - m_noclusterr(false) + m_noclusterr(false), + m_time_gran(1) {} virtual void SetUp(); diff --git a/tests/sys/fs/fusefs/write.cc b/tests/sys/fs/fusefs/write.cc index 147027d1b0ae..ae46a742af40 100644 --- a/tests/sys/fs/fusefs/write.cc +++ b/tests/sys/fs/fusefs/write.cc @@ -229,6 +229,14 @@ virtual void SetUp() { } }; +class TimeGran: public WriteBackAsync, public WithParamInterface { +public: +virtual void SetUp() { + m_time_gran = 1 << GetParam(); + WriteBackAsync::SetUp(); +} +}; + /* Tests for clustered writes with WriteBack cacheing */ class WriteCluster: public WriteBack { public: @@ -1135,6 +1143,43 @@ TEST_F(WriteBackAsync, timestamps_during_setattr) ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno); } +/* fuse_init_out.time_gran controls the granularity of timestamps */ +TEST_P(TimeGran, timestamps_during_setattr) +{ + 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; + const mode_t newmode = 0755; + int fd; + + expect_lookup(RELPATH, ino, 0); + expect_open(ino, 0, 1); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + uint32_t valid = FATTR_MODE | FATTR_MTIME | FATTR_CTIME; + return (in.header.opcode == FUSE_SETATTR && + in.header.nodeid == ino && + in.body.setattr.valid == valid && + in.body.setattr.mtimensec % m_time_gran == 0 && + in.body.setattr.ctimensec % m_time_gran == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, attr); + out.body.attr.attr.ino = ino; + out.body.attr.attr.mode = S_IFREG | newmode; + }))); + + fd = open(FULLPATH, O_RDWR); + EXPECT_LE(0, fd) << strerror(errno); + ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); + ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno); +} + +INSTANTIATE_TEST_CASE_P(RA, TimeGran, Range(0u, 10u)); + /* * Without direct_io, writes should be committed to cache */