113d593a5SAlan Somers /*-
24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause
313d593a5SAlan Somers *
413d593a5SAlan Somers * Copyright (c) 2021 Alan Somers
513d593a5SAlan Somers *
613d593a5SAlan Somers * Redistribution and use in source and binary forms, with or without
713d593a5SAlan Somers * modification, are permitted provided that the following conditions
813d593a5SAlan Somers * are met:
913d593a5SAlan Somers * 1. Redistributions of source code must retain the above copyright
1013d593a5SAlan Somers * notice, this list of conditions and the following disclaimer.
1113d593a5SAlan Somers * 2. Redistributions in binary form must reproduce the above copyright
1213d593a5SAlan Somers * notice, this list of conditions and the following disclaimer in the
1313d593a5SAlan Somers * documentation and/or other materials provided with the distribution.
1413d593a5SAlan Somers *
1513d593a5SAlan Somers * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1613d593a5SAlan Somers * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1713d593a5SAlan Somers * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1813d593a5SAlan Somers * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1913d593a5SAlan Somers * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2013d593a5SAlan Somers * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2113d593a5SAlan Somers * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2213d593a5SAlan Somers * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2313d593a5SAlan Somers * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2413d593a5SAlan Somers * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2513d593a5SAlan Somers * SUCH DAMAGE.
2613d593a5SAlan Somers */
2713d593a5SAlan Somers
2813d593a5SAlan Somers extern "C" {
2913d593a5SAlan Somers #include <sys/param.h>
3013d593a5SAlan Somers #include <sys/mount.h>
3113d593a5SAlan Somers #include <sys/stat.h>
3213d593a5SAlan Somers
3313d593a5SAlan Somers #include <fcntl.h>
3413d593a5SAlan Somers #include <pthread.h>
3513d593a5SAlan Somers #include <semaphore.h>
3613d593a5SAlan Somers }
3713d593a5SAlan Somers
3813d593a5SAlan Somers #include "mockfs.hh"
3913d593a5SAlan Somers #include "utils.hh"
4013d593a5SAlan Somers
4113d593a5SAlan Somers using namespace testing;
4213d593a5SAlan Somers
4313d593a5SAlan Somers /*
4413d593a5SAlan Somers * "Last Local Modify" bugs
4513d593a5SAlan Somers *
4613d593a5SAlan Somers * This file tests a class of race conditions caused by one thread fetching a
4713d593a5SAlan Somers * file's size with FUSE_LOOKUP while another thread simultaneously modifies it
4813d593a5SAlan Somers * with FUSE_SETATTR, FUSE_WRITE, FUSE_COPY_FILE_RANGE or similar. It's
4913d593a5SAlan Somers * possible for the second thread to start later yet finish first. If that
5013d593a5SAlan Somers * happens, the first thread must not override the size set by the second
5113d593a5SAlan Somers * thread.
5213d593a5SAlan Somers *
5313d593a5SAlan Somers * FUSE_GETATTR is not vulnerable to the same race, because it is always called
5413d593a5SAlan Somers * with the vnode lock held.
5513d593a5SAlan Somers *
5613d593a5SAlan Somers * A few other operations like FUSE_LINK can also trigger the same race but
5713d593a5SAlan Somers * with the file's ctime instead of size. However, the consequences of an
5813d593a5SAlan Somers * incorrect ctime are much less disastrous than an incorrect size, so fusefs
5913d593a5SAlan Somers * does not attempt to prevent such races.
6013d593a5SAlan Somers */
6113d593a5SAlan Somers
6213d593a5SAlan Somers enum Mutator {
63398c88c7SAlan Somers VOP_ALLOCATE,
64398c88c7SAlan Somers VOP_COPY_FILE_RANGE,
6513d593a5SAlan Somers VOP_SETATTR,
6613d593a5SAlan Somers VOP_WRITE,
6713d593a5SAlan Somers };
6813d593a5SAlan Somers
6913d593a5SAlan Somers /*
7013d593a5SAlan Somers * Translate a poll method's string representation to the enum value.
7113d593a5SAlan Somers * Using strings with ::testing::Values gives better output with
7213d593a5SAlan Somers * --gtest_list_tests
7313d593a5SAlan Somers */
writer_from_str(const char * s)7413d593a5SAlan Somers enum Mutator writer_from_str(const char* s) {
75398c88c7SAlan Somers if (0 == strcmp("VOP_ALLOCATE", s))
76398c88c7SAlan Somers return VOP_ALLOCATE;
77398c88c7SAlan Somers else if (0 == strcmp("VOP_COPY_FILE_RANGE", s))
7813d593a5SAlan Somers return VOP_COPY_FILE_RANGE;
79398c88c7SAlan Somers else if (0 == strcmp("VOP_SETATTR", s))
80398c88c7SAlan Somers return VOP_SETATTR;
81398c88c7SAlan Somers else
82398c88c7SAlan Somers return VOP_WRITE;
8313d593a5SAlan Somers }
8413d593a5SAlan Somers
fuse_op_from_mutator(enum Mutator mutator)8513d593a5SAlan Somers uint32_t fuse_op_from_mutator(enum Mutator mutator) {
8613d593a5SAlan Somers switch(mutator) {
87398c88c7SAlan Somers case VOP_ALLOCATE:
88398c88c7SAlan Somers return(FUSE_FALLOCATE);
89398c88c7SAlan Somers case VOP_COPY_FILE_RANGE:
90398c88c7SAlan Somers return(FUSE_COPY_FILE_RANGE);
9113d593a5SAlan Somers case VOP_SETATTR:
9213d593a5SAlan Somers return(FUSE_SETATTR);
9313d593a5SAlan Somers case VOP_WRITE:
9413d593a5SAlan Somers return(FUSE_WRITE);
9513d593a5SAlan Somers }
9613d593a5SAlan Somers }
9713d593a5SAlan Somers
9813d593a5SAlan Somers class LastLocalModify: public FuseTest, public WithParamInterface<const char*> {
9913d593a5SAlan Somers public:
SetUp()10013d593a5SAlan Somers virtual void SetUp() {
10113d593a5SAlan Somers m_init_flags = FUSE_EXPORT_SUPPORT;
10213d593a5SAlan Somers
10313d593a5SAlan Somers FuseTest::SetUp();
10413d593a5SAlan Somers }
10513d593a5SAlan Somers };
10613d593a5SAlan Somers
allocate_th(void * arg)107398c88c7SAlan Somers static void* allocate_th(void* arg) {
108398c88c7SAlan Somers int fd;
109398c88c7SAlan Somers ssize_t r;
110398c88c7SAlan Somers sem_t *sem = (sem_t*) arg;
111398c88c7SAlan Somers
112398c88c7SAlan Somers if (sem)
113398c88c7SAlan Somers sem_wait(sem);
114398c88c7SAlan Somers
115398c88c7SAlan Somers fd = open("mountpoint/some_file.txt", O_RDWR);
116398c88c7SAlan Somers if (fd < 0)
117398c88c7SAlan Somers return (void*)(intptr_t)errno;
118398c88c7SAlan Somers
119398c88c7SAlan Somers r = posix_fallocate(fd, 0, 15);
1204ac4b126SAlan Somers LastLocalModify::leak(fd);
121398c88c7SAlan Somers if (r >= 0)
122398c88c7SAlan Somers return 0;
123398c88c7SAlan Somers else
124398c88c7SAlan Somers return (void*)(intptr_t)errno;
125398c88c7SAlan Somers }
126398c88c7SAlan Somers
copy_file_range_th(void * arg)12713d593a5SAlan Somers static void* copy_file_range_th(void* arg) {
12813d593a5SAlan Somers ssize_t r;
12913d593a5SAlan Somers int fd;
13013d593a5SAlan Somers sem_t *sem = (sem_t*) arg;
13113d593a5SAlan Somers off_t off_in = 0;
13213d593a5SAlan Somers off_t off_out = 10;
13313d593a5SAlan Somers ssize_t len = 5;
13413d593a5SAlan Somers
13513d593a5SAlan Somers if (sem)
13613d593a5SAlan Somers sem_wait(sem);
13713d593a5SAlan Somers fd = open("mountpoint/some_file.txt", O_RDWR);
13813d593a5SAlan Somers if (fd < 0)
13913d593a5SAlan Somers return (void*)(intptr_t)errno;
14013d593a5SAlan Somers
14113d593a5SAlan Somers r = copy_file_range(fd, &off_in, fd, &off_out, len, 0);
14213d593a5SAlan Somers if (r >= 0) {
14313d593a5SAlan Somers LastLocalModify::leak(fd);
14413d593a5SAlan Somers return 0;
14513d593a5SAlan Somers } else
14613d593a5SAlan Somers return (void*)(intptr_t)errno;
14713d593a5SAlan Somers }
14813d593a5SAlan Somers
setattr_th(void * arg)14913d593a5SAlan Somers static void* setattr_th(void* arg) {
15013d593a5SAlan Somers int fd;
15113d593a5SAlan Somers ssize_t r;
15213d593a5SAlan Somers sem_t *sem = (sem_t*) arg;
15313d593a5SAlan Somers
15413d593a5SAlan Somers if (sem)
15513d593a5SAlan Somers sem_wait(sem);
15613d593a5SAlan Somers
15713d593a5SAlan Somers fd = open("mountpoint/some_file.txt", O_RDWR);
15813d593a5SAlan Somers if (fd < 0)
15913d593a5SAlan Somers return (void*)(intptr_t)errno;
16013d593a5SAlan Somers
16113d593a5SAlan Somers r = ftruncate(fd, 15);
1624ac4b126SAlan Somers LastLocalModify::leak(fd);
16313d593a5SAlan Somers if (r >= 0)
16413d593a5SAlan Somers return 0;
16513d593a5SAlan Somers else
16613d593a5SAlan Somers return (void*)(intptr_t)errno;
16713d593a5SAlan Somers }
16813d593a5SAlan Somers
write_th(void * arg)16913d593a5SAlan Somers static void* write_th(void* arg) {
17013d593a5SAlan Somers ssize_t r;
17113d593a5SAlan Somers int fd;
17213d593a5SAlan Somers sem_t *sem = (sem_t*) arg;
17313d593a5SAlan Somers const char BUF[] = "abcdefghijklmn";
17413d593a5SAlan Somers
17513d593a5SAlan Somers if (sem)
17613d593a5SAlan Somers sem_wait(sem);
17713d593a5SAlan Somers fd = open("mountpoint/some_file.txt", O_RDWR);
17813d593a5SAlan Somers if (fd < 0)
17913d593a5SAlan Somers return (void*)(intptr_t)errno;
18013d593a5SAlan Somers
18113d593a5SAlan Somers r = write(fd, BUF, sizeof(BUF));
18213d593a5SAlan Somers if (r >= 0) {
18313d593a5SAlan Somers LastLocalModify::leak(fd);
18413d593a5SAlan Somers return 0;
18513d593a5SAlan Somers } else
18613d593a5SAlan Somers return (void*)(intptr_t)errno;
18713d593a5SAlan Somers }
18813d593a5SAlan Somers
18913d593a5SAlan Somers /*
19013d593a5SAlan Somers * VOP_LOOKUP should discard attributes returned by the server if they were
19113d593a5SAlan Somers * modified by another VOP while the VOP_LOOKUP was in progress.
19213d593a5SAlan Somers *
19313d593a5SAlan Somers * Sequence of operations:
19413d593a5SAlan Somers * * Thread 1 calls a mutator like ftruncate, which acquires the vnode lock
19513d593a5SAlan Somers * exclusively.
19613d593a5SAlan Somers * * Thread 2 calls stat, which does VOP_LOOKUP, which sends FUSE_LOOKUP to the
19713d593a5SAlan Somers * server. The server replies with the old file length. Thread 2 blocks
19813d593a5SAlan Somers * waiting for the vnode lock.
19913d593a5SAlan Somers * * Thread 1 sends the mutator operation like FUSE_SETATTR that changes the
20013d593a5SAlan Somers * file's size and updates the attribute cache. Then it releases the vnode
20113d593a5SAlan Somers * lock.
20213d593a5SAlan Somers * * Thread 2 acquires the vnode lock. At this point it must not add the
20313d593a5SAlan Somers * now-stale file size to the attribute cache.
20413d593a5SAlan Somers *
20513d593a5SAlan Somers * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259071
20613d593a5SAlan Somers */
TEST_P(LastLocalModify,lookup)20713d593a5SAlan Somers TEST_P(LastLocalModify, lookup)
20813d593a5SAlan Somers {
20913d593a5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
21013d593a5SAlan Somers const char RELPATH[] = "some_file.txt";
21113d593a5SAlan Somers Sequence seq;
21213d593a5SAlan Somers uint64_t ino = 3;
21313d593a5SAlan Somers uint64_t mutator_unique;
21413d593a5SAlan Somers const uint64_t oldsize = 10;
21513d593a5SAlan Somers const uint64_t newsize = 15;
21613d593a5SAlan Somers pthread_t th0;
21713d593a5SAlan Somers void *thr0_value;
21813d593a5SAlan Somers struct stat sb;
21913d593a5SAlan Somers static sem_t sem;
22013d593a5SAlan Somers Mutator mutator;
22113d593a5SAlan Somers uint32_t mutator_op;
22213d593a5SAlan Somers size_t mutator_size;
22313d593a5SAlan Somers
22413d593a5SAlan Somers mutator = writer_from_str(GetParam());
22513d593a5SAlan Somers mutator_op = fuse_op_from_mutator(mutator);
22613d593a5SAlan Somers
22713d593a5SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
22813d593a5SAlan Somers
22913d593a5SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
23013d593a5SAlan Somers .InSequence(seq)
23113d593a5SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
23213d593a5SAlan Somers /* Called by the mutator, caches attributes but not entries */
23313d593a5SAlan Somers SET_OUT_HEADER_LEN(out, entry);
23413d593a5SAlan Somers out.body.entry.nodeid = ino;
23513d593a5SAlan Somers out.body.entry.attr.size = oldsize;
23613d593a5SAlan Somers out.body.entry.nodeid = ino;
23713d593a5SAlan Somers out.body.entry.attr_valid_nsec = NAP_NS / 2;
23813d593a5SAlan Somers out.body.entry.attr.ino = ino;
23913d593a5SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
24013d593a5SAlan Somers })));
24113d593a5SAlan Somers expect_open(ino, 0, 1);
24213d593a5SAlan Somers EXPECT_CALL(*m_mock, process(
24313d593a5SAlan Somers ResultOf([=](auto in) {
24413d593a5SAlan Somers return (in.header.opcode == mutator_op &&
24513d593a5SAlan Somers in.header.nodeid == ino);
24613d593a5SAlan Somers }, Eq(true)),
24713d593a5SAlan Somers _)
24813d593a5SAlan Somers ).InSequence(seq)
24913d593a5SAlan Somers .WillOnce(Invoke([&](auto in, auto &out __unused) {
25013d593a5SAlan Somers /*
25113d593a5SAlan Somers * The mutator changes the file size, but in order to simulate
25213d593a5SAlan Somers * a race, don't reply. Instead, just save the unique for
25313d593a5SAlan Somers * later.
25413d593a5SAlan Somers */
25513d593a5SAlan Somers mutator_unique = in.header.unique;
25613d593a5SAlan Somers switch(mutator) {
25713d593a5SAlan Somers case VOP_WRITE:
25813d593a5SAlan Somers mutator_size = in.body.write.size;
25913d593a5SAlan Somers break;
26013d593a5SAlan Somers case VOP_COPY_FILE_RANGE:
26113d593a5SAlan Somers mutator_size = in.body.copy_file_range.len;
26213d593a5SAlan Somers break;
26313d593a5SAlan Somers default:
26413d593a5SAlan Somers break;
26513d593a5SAlan Somers }
26613d593a5SAlan Somers /* Allow the lookup thread to proceed */
26713d593a5SAlan Somers sem_post(&sem);
26813d593a5SAlan Somers }));
26913d593a5SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
27013d593a5SAlan Somers .InSequence(seq)
27113d593a5SAlan Somers .WillOnce(Invoke([&](auto in __unused, auto& out) {
27213d593a5SAlan Somers std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
27313d593a5SAlan Somers std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
27413d593a5SAlan Somers
27513d593a5SAlan Somers /* First complete the lookup request, returning the old size */
27613d593a5SAlan Somers out0->header.unique = in.header.unique;
27713d593a5SAlan Somers SET_OUT_HEADER_LEN(*out0, entry);
27813d593a5SAlan Somers out0->body.entry.attr.mode = S_IFREG | 0644;
27913d593a5SAlan Somers out0->body.entry.nodeid = ino;
28013d593a5SAlan Somers out0->body.entry.entry_valid = UINT64_MAX;
28113d593a5SAlan Somers out0->body.entry.attr_valid = UINT64_MAX;
28213d593a5SAlan Somers out0->body.entry.attr.size = oldsize;
28313d593a5SAlan Somers out.push_back(std::move(out0));
28413d593a5SAlan Somers
28513d593a5SAlan Somers /* Then, respond to the mutator request */
28613d593a5SAlan Somers out1->header.unique = mutator_unique;
28713d593a5SAlan Somers switch(mutator) {
288398c88c7SAlan Somers case VOP_ALLOCATE:
289398c88c7SAlan Somers out1->header.error = 0;
290398c88c7SAlan Somers out1->header.len = sizeof(out1->header);
291398c88c7SAlan Somers break;
292398c88c7SAlan Somers case VOP_COPY_FILE_RANGE:
293398c88c7SAlan Somers SET_OUT_HEADER_LEN(*out1, write);
294398c88c7SAlan Somers out1->body.write.size = mutator_size;
295398c88c7SAlan Somers break;
29613d593a5SAlan Somers case VOP_SETATTR:
29713d593a5SAlan Somers SET_OUT_HEADER_LEN(*out1, attr);
29813d593a5SAlan Somers out1->body.attr.attr.ino = ino;
29913d593a5SAlan Somers out1->body.attr.attr.mode = S_IFREG | 0644;
30013d593a5SAlan Somers out1->body.attr.attr.size = newsize; // Changed size
30113d593a5SAlan Somers out1->body.attr.attr_valid = UINT64_MAX;
30213d593a5SAlan Somers break;
30313d593a5SAlan Somers case VOP_WRITE:
30413d593a5SAlan Somers SET_OUT_HEADER_LEN(*out1, write);
30513d593a5SAlan Somers out1->body.write.size = mutator_size;
30613d593a5SAlan Somers break;
30713d593a5SAlan Somers }
30813d593a5SAlan Somers out.push_back(std::move(out1));
30913d593a5SAlan Somers }));
31013d593a5SAlan Somers
31113d593a5SAlan Somers /* Start the mutator thread */
31213d593a5SAlan Somers switch(mutator) {
313398c88c7SAlan Somers case VOP_ALLOCATE:
314398c88c7SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th,
315398c88c7SAlan Somers NULL)) << strerror(errno);
316398c88c7SAlan Somers break;
317398c88c7SAlan Somers case VOP_COPY_FILE_RANGE:
318398c88c7SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th,
319398c88c7SAlan Somers NULL)) << strerror(errno);
320398c88c7SAlan Somers break;
32113d593a5SAlan Somers case VOP_SETATTR:
32213d593a5SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, NULL))
32313d593a5SAlan Somers << strerror(errno);
32413d593a5SAlan Somers break;
32513d593a5SAlan Somers case VOP_WRITE:
32613d593a5SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, NULL))
32713d593a5SAlan Somers << strerror(errno);
32813d593a5SAlan Somers break;
32913d593a5SAlan Somers }
33013d593a5SAlan Somers
33113d593a5SAlan Somers
33213d593a5SAlan Somers /* Wait for FUSE_SETATTR to be sent */
33313d593a5SAlan Somers sem_wait(&sem);
33413d593a5SAlan Somers
33513d593a5SAlan Somers /* Lookup again, which will race with setattr */
33613d593a5SAlan Somers ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
33713d593a5SAlan Somers ASSERT_EQ((off_t)newsize, sb.st_size);
33813d593a5SAlan Somers
33913d593a5SAlan Somers /* ftruncate should've completed without error */
34013d593a5SAlan Somers pthread_join(th0, &thr0_value);
34113d593a5SAlan Somers EXPECT_EQ(0, (intptr_t)thr0_value);
34213d593a5SAlan Somers }
34313d593a5SAlan Somers
34413d593a5SAlan Somers /*
34513d593a5SAlan Somers * VFS_VGET should discard attributes returned by the server if they were
34613d593a5SAlan Somers * modified by another VOP while the VFS_VGET was in progress.
34713d593a5SAlan Somers *
34813d593a5SAlan Somers * Sequence of operations:
34913d593a5SAlan Somers * * Thread 1 calls fhstat, entering VFS_VGET, and issues FUSE_LOOKUP
35013d593a5SAlan Somers * * Thread 2 calls a mutator like ftruncate, which acquires the vnode lock
35113d593a5SAlan Somers * exclusively and issues a FUSE op like FUSE_SETATTR.
35213d593a5SAlan Somers * * Thread 1's FUSE_LOOKUP returns with the old size, but the thread blocks
35313d593a5SAlan Somers * waiting for the vnode lock.
35413d593a5SAlan Somers * * Thread 2's FUSE op returns, and that thread sets the file's new size
35513d593a5SAlan Somers * in the attribute cache. Finally it releases the vnode lock.
35613d593a5SAlan Somers * * The vnode lock acquired, thread 1 must not overwrite the attr cache's size
35713d593a5SAlan Somers * with the old value.
35813d593a5SAlan Somers *
35913d593a5SAlan Somers * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259071
36013d593a5SAlan Somers */
TEST_P(LastLocalModify,vfs_vget)36113d593a5SAlan Somers TEST_P(LastLocalModify, vfs_vget)
36213d593a5SAlan Somers {
36313d593a5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
36413d593a5SAlan Somers const char RELPATH[] = "some_file.txt";
36513d593a5SAlan Somers Sequence seq;
36613d593a5SAlan Somers uint64_t ino = 3;
36713d593a5SAlan Somers uint64_t lookup_unique;
36813d593a5SAlan Somers const uint64_t oldsize = 10;
36913d593a5SAlan Somers const uint64_t newsize = 15;
37013d593a5SAlan Somers pthread_t th0;
37113d593a5SAlan Somers void *thr0_value;
37213d593a5SAlan Somers struct stat sb;
37313d593a5SAlan Somers static sem_t sem;
37413d593a5SAlan Somers fhandle_t fhp;
37513d593a5SAlan Somers Mutator mutator;
37613d593a5SAlan Somers uint32_t mutator_op;
37713d593a5SAlan Somers
37813d593a5SAlan Somers if (geteuid() != 0)
37913d593a5SAlan Somers GTEST_SKIP() << "This test requires a privileged user";
38013d593a5SAlan Somers
38113d593a5SAlan Somers mutator = writer_from_str(GetParam());
38213d593a5SAlan Somers mutator_op = fuse_op_from_mutator(mutator);
38313d593a5SAlan Somers
38413d593a5SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
38513d593a5SAlan Somers
38613d593a5SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
38713d593a5SAlan Somers .Times(1)
38813d593a5SAlan Somers .InSequence(seq)
38913d593a5SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
39013d593a5SAlan Somers {
39113d593a5SAlan Somers /* Called by getfh, caches attributes but not entries */
39213d593a5SAlan Somers SET_OUT_HEADER_LEN(out, entry);
39313d593a5SAlan Somers out.body.entry.nodeid = ino;
39413d593a5SAlan Somers out.body.entry.attr.size = oldsize;
39513d593a5SAlan Somers out.body.entry.nodeid = ino;
39613d593a5SAlan Somers out.body.entry.attr_valid_nsec = NAP_NS / 2;
39713d593a5SAlan Somers out.body.entry.attr.ino = ino;
39813d593a5SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
39913d593a5SAlan Somers })));
40013d593a5SAlan Somers EXPECT_LOOKUP(ino, ".")
40113d593a5SAlan Somers .InSequence(seq)
40213d593a5SAlan Somers .WillOnce(Invoke([&](auto in, auto &out __unused) {
40313d593a5SAlan Somers /* Called by fhstat. Block to simulate a race */
40413d593a5SAlan Somers lookup_unique = in.header.unique;
40513d593a5SAlan Somers sem_post(&sem);
40613d593a5SAlan Somers }));
40713d593a5SAlan Somers
40813d593a5SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
40913d593a5SAlan Somers .Times(1)
41013d593a5SAlan Somers .InSequence(seq)
41113d593a5SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
41213d593a5SAlan Somers {
41313d593a5SAlan Somers /* Called by VOP_SETATTR, caches attributes but not entries */
41413d593a5SAlan Somers SET_OUT_HEADER_LEN(out, entry);
41513d593a5SAlan Somers out.body.entry.nodeid = ino;
41613d593a5SAlan Somers out.body.entry.attr.size = oldsize;
41713d593a5SAlan Somers out.body.entry.nodeid = ino;
41813d593a5SAlan Somers out.body.entry.attr_valid_nsec = NAP_NS / 2;
41913d593a5SAlan Somers out.body.entry.attr.ino = ino;
42013d593a5SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
42113d593a5SAlan Somers })));
42213d593a5SAlan Somers
42313d593a5SAlan Somers /* Called by the mutator thread */
42413d593a5SAlan Somers expect_open(ino, 0, 1);
42513d593a5SAlan Somers
42613d593a5SAlan Somers EXPECT_CALL(*m_mock, process(
42713d593a5SAlan Somers ResultOf([=](auto in) {
42813d593a5SAlan Somers return (in.header.opcode == mutator_op &&
42913d593a5SAlan Somers in.header.nodeid == ino);
43013d593a5SAlan Somers }, Eq(true)),
43113d593a5SAlan Somers _)
43213d593a5SAlan Somers ).InSequence(seq)
43313d593a5SAlan Somers .WillOnce(Invoke([&](auto in __unused, auto& out) {
43413d593a5SAlan Somers std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
43513d593a5SAlan Somers std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
43613d593a5SAlan Somers
43713d593a5SAlan Somers /* First complete the lookup request, returning the old size */
43813d593a5SAlan Somers out0->header.unique = lookup_unique;
43913d593a5SAlan Somers SET_OUT_HEADER_LEN(*out0, entry);
44013d593a5SAlan Somers out0->body.entry.attr.mode = S_IFREG | 0644;
44113d593a5SAlan Somers out0->body.entry.nodeid = ino;
44213d593a5SAlan Somers out0->body.entry.entry_valid = UINT64_MAX;
44313d593a5SAlan Somers out0->body.entry.attr_valid = UINT64_MAX;
44413d593a5SAlan Somers out0->body.entry.attr.size = oldsize;
44513d593a5SAlan Somers out.push_back(std::move(out0));
44613d593a5SAlan Somers
44713d593a5SAlan Somers /* Then, respond to the mutator request */
44813d593a5SAlan Somers out1->header.unique = in.header.unique;
44913d593a5SAlan Somers switch(mutator) {
450398c88c7SAlan Somers case VOP_ALLOCATE:
451398c88c7SAlan Somers out1->header.error = 0;
452398c88c7SAlan Somers out1->header.len = sizeof(out1->header);
453398c88c7SAlan Somers break;
454398c88c7SAlan Somers case VOP_COPY_FILE_RANGE:
455398c88c7SAlan Somers SET_OUT_HEADER_LEN(*out1, write);
456398c88c7SAlan Somers out1->body.write.size = in.body.copy_file_range.len;
457398c88c7SAlan Somers break;
45813d593a5SAlan Somers case VOP_SETATTR:
45913d593a5SAlan Somers SET_OUT_HEADER_LEN(*out1, attr);
46013d593a5SAlan Somers out1->body.attr.attr.ino = ino;
46113d593a5SAlan Somers out1->body.attr.attr.mode = S_IFREG | 0644;
46213d593a5SAlan Somers out1->body.attr.attr.size = newsize; // Changed size
46313d593a5SAlan Somers out1->body.attr.attr_valid = UINT64_MAX;
46413d593a5SAlan Somers break;
46513d593a5SAlan Somers case VOP_WRITE:
46613d593a5SAlan Somers SET_OUT_HEADER_LEN(*out1, write);
46713d593a5SAlan Somers out1->body.write.size = in.body.write.size;
46813d593a5SAlan Somers break;
46913d593a5SAlan Somers }
47013d593a5SAlan Somers out.push_back(std::move(out1));
47113d593a5SAlan Somers }));
47213d593a5SAlan Somers
47313d593a5SAlan Somers /* First get a file handle */
47413d593a5SAlan Somers ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
47513d593a5SAlan Somers
47613d593a5SAlan Somers /* Start the mutator thread */
47713d593a5SAlan Somers switch(mutator) {
478398c88c7SAlan Somers case VOP_ALLOCATE:
479398c88c7SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th,
480398c88c7SAlan Somers (void*)&sem)) << strerror(errno);
481398c88c7SAlan Somers break;
482398c88c7SAlan Somers case VOP_COPY_FILE_RANGE:
483398c88c7SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th,
484398c88c7SAlan Somers (void*)&sem)) << strerror(errno);
485398c88c7SAlan Somers break;
48613d593a5SAlan Somers case VOP_SETATTR:
48713d593a5SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th,
48813d593a5SAlan Somers (void*)&sem)) << strerror(errno);
48913d593a5SAlan Somers break;
49013d593a5SAlan Somers case VOP_WRITE:
49113d593a5SAlan Somers ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, (void*)&sem))
49213d593a5SAlan Somers << strerror(errno);
49313d593a5SAlan Somers break;
49413d593a5SAlan Somers }
49513d593a5SAlan Somers
49613d593a5SAlan Somers /* Lookup again, which will race with setattr */
49713d593a5SAlan Somers ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
49813d593a5SAlan Somers
49913d593a5SAlan Somers ASSERT_EQ((off_t)newsize, sb.st_size);
50013d593a5SAlan Somers
50113d593a5SAlan Somers /* mutator should've completed without error */
50213d593a5SAlan Somers pthread_join(th0, &thr0_value);
50313d593a5SAlan Somers EXPECT_EQ(0, (intptr_t)thr0_value);
50413d593a5SAlan Somers }
50513d593a5SAlan Somers
50613d593a5SAlan Somers
507*811e0a31SEnji Cooper INSTANTIATE_TEST_SUITE_P(LLM, LastLocalModify,
508398c88c7SAlan Somers Values(
509398c88c7SAlan Somers "VOP_ALLOCATE",
510398c88c7SAlan Somers "VOP_COPY_FILE_RANGE",
511398c88c7SAlan Somers "VOP_SETATTR",
512398c88c7SAlan Somers "VOP_WRITE"
513398c88c7SAlan Somers )
51413d593a5SAlan Somers );
515